Writing End-to-End Tests With Spock Framework – Rendering Data
After we have finished this lesson, we:
- Know how we can write end-to-end tests for features that render data.
- Understand how we should structure our specification classes when we specify the behavior of the system under specification.
This lesson assumes that:
- You can run your end-to-end tests by using either Maven or Gradle
- You are familiar with Selenium WebDriver
- You can write end-to-end tests for Spring web applications by using JUnit
- You are familiar with Spock Framework
- You can configure your end-to-end tests when you are using Spring Framework or Spring Boot.
Important note:
You must finish the lessons: Writing End-to-End Tests for Spring Web Applications – Rendering a Single Item and Writing End-to-End Tests for Spring Web Applications – Rendering a List before you continue this lesson. These lessons describe the requirements of the tested features, explain how you can create the required page objects, and describe how you can write the required constant classes.
Because I don’t want to repeat myself, I won’t explain these things in this lesson. The goal of this lesson is to simply demonstrate how you can write end-to-end tests for features that render data.
Watch the Lesson
The text version of this lesson is given in the following:
Scenario 1: Task List Feature
We can specify the expected behavior of the task list feature by following these steps:
First, we have to create a new specification class and configure Selenium WebDriver. After we have done this, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import org.openqa.selenium.WebDriver import org.openqa.selenium.chrome.ChromeDriver import spock.lang.Shared import spock.lang.Specification @SeleniumTest(driver = ChromeDriver.class) @Category(EndToEndTest.class) class RenderTaskListPageSpec extends Specification { @SeleniumWebDriver @Shared WebDriver browser }
Second, we have to add a TaskListPage
field to our specification class. Our feature method uses this field when it opens the task list page.
After we have added this field to our specification class, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import org.openqa.selenium.WebDriver import org.openqa.selenium.chrome.ChromeDriver import spock.lang.Shared import spock.lang.Specification @SeleniumTest(driver = ChromeDriver.class) @Category(EndToEndTest.class) class RenderTaskListPageSpec extends Specification { @SeleniumWebDriver @Shared WebDriver browser TaskListPage taskListPage }
Third, we have to add a setup()
method to our specification class and implement this method by creating a new TaskListPage
object. After we have created this object, we have to store the created object in the taskListPage
field.
After we have implemented the setup()
method, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import org.openqa.selenium.WebDriver import org.openqa.selenium.chrome.ChromeDriver import spock.lang.Shared import spock.lang.Specification @SeleniumTest(driver = ChromeDriver.class) @Category(EndToEndTest.class) class RenderTaskListPageSpec extends Specification { @SeleniumWebDriver @Shared WebDriver browser TaskListPage taskListPage def setup() { taskListPage = new TaskListPage(browser) } }
Fourth, we have to ensure that the authenticated user is logged out after a feature method has been run. This is a very important step because it helps us to ensure that the correct user is logged in when an end-to-end test is run.
After we have implemented the cleanup()
method, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import org.openqa.selenium.WebDriver import org.openqa.selenium.chrome.ChromeDriver import spock.lang.Shared import spock.lang.Specification @SeleniumTest(driver = ChromeDriver.class) @Category(EndToEndTest.class) class RenderTaskListPageSpec extends Specification { @SeleniumWebDriver @Shared WebDriver browser TaskListPage taskListPage def setup() { taskListPage = new TaskListPage(browser) } def cleanup() { new NavigationBar(browser).logUserOut() } }
Fifth, we have to write a feature method that specifies the behavior of the task list feature when a registered user opens the task list page. If we are initializing our database into a known state when our web application is started, writing these end-to-end tests is a bit tricky because we cannot know the order in which our end-to-end tests are run.
If we are writing end-to-end tests for a Spring Boot web application, we don’t have this problem because we can initialize our database into a known state before a feature method is run.
Because our example uses Spring Framework, we can write our feature method by following these steps:
- Add a new feature method to our specification class.
- Define a variable that contains the page object which represents the opened task list page.
- Open the login page and log the user in.
- Open the task list page and store the returned page object in the
shownPage
variable. We must use this variable when we write assertions for the expected results. - Ensure that the system under specification opened the task list page.
- Verify that the task list page displays at least two tasks. Because our SQL script contains three tasks and we want to write end-to-end tests for features that delete existing tasks and create new tasks, our database contains at least two tasks when this feature method is run.
- Ensure that the task list page displays the correct information of the first task. Again, this assertion seems a bit weird. However, because we use the other tasks found from our SQL script when we write end-to-end tests for the delete and update task features, we don’t know what their field values are when this feature method is run. That’s why we can assert only the field values of the first task.
- Verify that a user can navigate from the task list page to the view task page.
After we have written this feature method, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import org.openqa.selenium.WebDriver import org.openqa.selenium.chrome.ChromeDriver import spock.lang.Shared import spock.lang.Specification import static com.testwithspring.master.EndToEndTestUsers.JohnDoe @SeleniumTest(driver = ChromeDriver.class) @Category(EndToEndTest.class) class RenderTaskListPageSpec extends Specification { @SeleniumWebDriver @Shared WebDriver browser TaskListPage taskListPage def setup() { taskListPage = new TaskListPage(browser) } def 'Open task list page as a registered user'() { TaskListPage shownPage given: 'A user is authenticated successfully' LoginPage loginPage = new LoginPage(browser).open() loginPage.login(JohnDoe.EMAIL_ADDRESS, JohnDoe.PASSWORD) when: 'A user opens the task list page' shownPage = taskListPage.open() then: 'Should open the task list page' shownPage.isOpen() and: 'Should display task list that has at least two tasks' def tasks = shownPage.getListItems() tasks.size() >= 2 and: 'Should show the correct information of the first task' def firstTask = tasks.get(0) firstTask.id == EndToEndTestTasks.WriteExampleApp.ID firstTask.title == EndToEndTestTasks.WriteExampleApp.TITLE firstTask.status == EndToEndTestTasks.WriteExampleApp.STATUS and: 'A user must be able to navigate to the view task page' def taskPage = firstTask.viewTask() taskPage.isOpen() } def cleanup() { new NavigationBar(browser).logUserOut() } }
Additional Reading:
Next, we will find out how we can specify the expected behavior of the view task feature.
Scenario 2: View Task Feature
We can specify the expected behavior of the view task feature by following these steps:
First, we have to create a new specification class and configure Selenium WebDriver. After we have done this, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import org.openqa.selenium.WebDriver import org.openqa.selenium.chrome.ChromeDriver import spock.lang.Shared import spock.lang.Specification @SeleniumTest(driver = ChromeDriver.class) @Category(EndToEndTest.class) class RenderViewTaskPageSpec extends Specification { @SeleniumWebDriver @Shared WebDriver browser }
Second, we have to add a TaskPage
field to our specification class. Our feature method uses this field when it opens the view task page.
After we have added this field to our specification class, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import org.openqa.selenium.WebDriver import org.openqa.selenium.chrome.ChromeDriver import spock.lang.Shared import spock.lang.Specification @SeleniumTest(driver = ChromeDriver.class) @Category(EndToEndTest.class) class RenderViewTaskPageSpec extends Specification { @SeleniumWebDriver @Shared WebDriver browser TaskPage viewTaskPage }
Third, we have to add a setup()
method to our specification class and implement this method by creating a new TaskPage
object. After we have created this object, we have to store the created object in the viewTaskPage
field.
After we have implemented the setup()
method, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import org.openqa.selenium.WebDriver import org.openqa.selenium.chrome.ChromeDriver import spock.lang.Shared import spock.lang.Specification @SeleniumTest(driver = ChromeDriver.class) @Category(EndToEndTest.class) class RenderViewTaskPageSpec extends Specification { @SeleniumWebDriver @Shared WebDriver browser TaskPage viewTaskPage def setup() { viewTaskPage = new TaskPage(browser, EndToEndTestTasks.WriteExampleApp.ID ) } }
Fourth, we have to ensure that the authenticated user is logged out after a feature method has been run. This is a very important step because it helps us to ensure that the correct user is logged in when an end-to-end test is run.
After we have implemented the cleanup()
method, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import org.openqa.selenium.WebDriver import org.openqa.selenium.chrome.ChromeDriver import spock.lang.Shared import spock.lang.Specification @SeleniumTest(driver = ChromeDriver.class) @Category(EndToEndTest.class) class RenderViewTaskPageSpec extends Specification { @SeleniumWebDriver @Shared WebDriver browser TaskPage viewTaskPage def setup() { viewTaskPage = new TaskPage(browser, EndToEndTestTasks.WriteExampleApp.ID ) } def cleanup() { new NavigationBar(browser).logUserOut() } }
Fifth, we have to write a feature method which specifies the behavior of the view task feature when a registered user opens the view task page that displays the information of a closed task. We can write this feature method by following these steps:
- Add a new feature method to our specification class.
- Define a variable that contains the page object which represents the opened view task page.
- Open the login page and log the user in.
- Open the view task page and store the returned page object in the
shownPage
variable. We must use this variable when we write assertions for the expected results. - Ensure that the system under specification opened the view task page.
- Verify that the view task page displays the title of the shown task.
- Ensure that the view task page displays the description of the shown task.
- Verify that the view task page displays the information of the assignee.
- Ensure that the view task page displays the name of the assignee.
- Verify that the view task page displays the name of the person who created the task.
- Ensure that the view task page displays the name of the person who modified the task.
- Verify that the view task page displays the lifecycle fields of a closed task.
- Ensure that the view task page displays the name of the person who closed the task.
- Verify that a user can navigate to the update task page.
After we have written our feature method, the source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import org.openqa.selenium.WebDriver import org.openqa.selenium.chrome.ChromeDriver import spock.lang.Shared import spock.lang.Specification import static com.testwithspring.master.EndToEndTestUsers.JohnDoe @SeleniumTest(driver = ChromeDriver.class) @Category(EndToEndTest.class) class RenderViewTaskPageSpec extends Specification { @SeleniumWebDriver @Shared WebDriver browser TaskPage viewTaskPage def setup() { viewTaskPage = new TaskPage(browser, EndToEndTestTasks.WriteExampleApp.ID ) } def 'Open view task page as a registered user'() { TaskPage shownPage given: 'A user is authenticated successfully' LoginPage loginPage = new LoginPage(browser).open() loginPage.login(JohnDoe.EMAIL_ADDRESS, JohnDoe.PASSWORD) when: 'A user opens the view task page of a closed task' shownPage = viewTaskPage.open() then: 'Should open the view task page' shownPage.isOpen() and: 'Should display the title of the shown task' shownPage.getTaskTitle() == EndToEndTestTasks.WriteExampleApp.TITLE and: 'Should display the description of the shown task' shownPage.getTaskDescription() == EndToEndTestTasks.WriteExampleApp.DESCRIPTION and: 'Should display the information of the assignee' def lifecycleFields = shownPage.getTaskLifecycleFields() lifecycleFields.isAssigneeNameVisible() and: 'Should display the name of the assignee' lifecycleFields.getAssigneeName() == EndToEndTestTasks.WriteExampleApp.Assignee.NAME and: 'Should display the name of the creator' lifecycleFields.getCreatorName() == EndToEndTestTasks.WriteExampleApp.Creator.NAME and: 'Should display the name of the modifier' lifecycleFields.getModifierName() == EndToEndTestTasks.WriteExampleApp.Modifier.NAME and: 'Should display the lifecycle fields of a closed task' lifecycleFields.areClosedTaskFieldsVisible() and: 'Should display the name of the closer' lifecycleFields.getCloserName() == EndToEndTestTasks.WriteExampleApp.Closer.NAME and: 'A user must be able to navigate to the update task page' def actions = shownPage.getTaskActions() UpdateTaskPage updateTaskPage = actions.updateTask() updateTaskPage.isOpen() } def cleanup() { new NavigationBar(browser).logUserOut() } }
We can now write end-to-end tests for features that render data. Let’s move on and find out how we should structure our specification classes.
Structuring Our Specification Class
We should structure our specification classes by following these rules:
- If we are writing end-to-end tests for a Spring web application, and the tested feature renders all data found from a database table, we can write only one specification class. Because we initialize our database into a known state when our web application is started, the database table will always contain data. In other words, it doesn’t make any sense to write multiple specification classes because we cannot verify that the system under specification is working as expected when the database table is empty.
- If we are writing end-to-end tests for a Spring Boot web application, and the tested feature renders all data found from a database table, we should write multiple specification classes because otherwise we cannot ensure that the system under specification is working as expected when the database table is empty and when it contains data.
- If the tested feature renders data that fulfills the given search criteria, we should write only one specification class. We don’t have to write multiple specification classes because we can construct our test data and select the used search criteria in a way that allows us to test all required scenarios by using multiple when and then blocks.
- We should write one feature method per user role. This helps us to minimize the number of specification classes, and write end-to-end tests which have a clean and logical structure. Also, if we follow this rule, the created HTML documentation looks logical as well.
We can now write end-to-end tests for features that render data and structure our specification classes in the “correct” way. Let’s summarize what we learned from this lesson.
Summary
This lesson has taught us four things:
- If we are writing end-to-end tests for a Spring web application, and the tested feature renders all data found from a database table, we can write only one specification class.
- If we are writing end-to-end tests for a Spring Boot web application, and the tested feature renders all data found from a database table, we should write multiple specification classes.
- If the tested feature renders data that fulfills the given search criteria, we should write only one specification class.
- We should write one feature method per user role.
Previous LessonNext Lesson