The Project @Test methods in the class ProjectTest demonstrate the level of testing
that takes advantage of the TracksApi abstractions.
These @Test methods do not use RestAssured and HttpMessageSender directly
because they are exploring entity level API messages, rather than HTTP format and endpoint access.
The ProjectTest class only covers three basic scenarios, but this was enough to
flush out TracksApi abstraction to cover GET, POST, PUT and DELETE scenarios.
Create Project
All of the REST API interaction in this test is carried out through the TracksApi
abstraction. Points to note:
The simple random data generator RandomDataGenerator is used to create
unique test data into the environment.
Assertions use the JUnit assertions rather than REST Assured.
RestAssured is used, but only because of a bleed over of the Response class.
I named the instantiated TracksApi variable as api. This results in the test being
easy to read and understand because it is not cluttered by the actual implementation details of the API calls.
@Test
public void aUserCanCreateAProject(){
TracksApi api = new TracksApi(TestEnvDefaults.getTestEnv()); // get the current set of projects
int totalProjectsForUser = api.getProjects().size(); // create a new project
String newProjectName = "A New Project" +
new RandomDataGenerator().randomWord(); Response response = api.createProject(newProjectName);
Assert.assertEquals(201, response.getStatusCode());
// get projects again and check the new project is in the list
List<TracksProject> theProjects = api.getProjects(); int newTotalProjectsForUser = theProjects.size(); Assert.assertTrue(newTotalProjectsForUser >
totalProjectsForUser); Boolean foundProject = false;
for(TracksProject project : theProjects){
if(project.getName().contentEquals(newProjectName)){ foundProject = true;
} }
Assert.assertTrue("Could not find project named " + newProjectName, foundProject); }
A few words about the assertions.
After creating a Project through the API and asserting on the response status code.
Response response = api.createProject(newProjectName); Assert.assertEquals(201, response.getStatusCode());
Check that the size of the Project list has increased.
List<TracksProject> theProjects = api.getProjects(); int newTotalProjectsForUser = theProjects.size(); Assert.assertTrue(newTotalProjectsForUser >
totalProjectsForUser);
Also check that the Project created was actually returned in the list of Projects.
Boolean foundProject = false;
for(TracksProject project : theProjects){
if(project.getName().contentEquals(newProjectName)){ foundProject = true;
} }
Assert.assertTrue("Could not find project named " + newProjectName, foundProject);
This might be viewed as overkill, but it avoids false positives where the total has increased, but the created value is not accessible.
The size check is performed first, because if the list hasn’t increased in size then we wouldn’t expect to find the Project, and there is no point spending time working through the list.
Amend
The ‘amend’ test creates a Project, and even repeats some of the assertions from the ‘Create’ test. i.e. checking for a 201 status code on creation.
Some people would view this as duplication and would not have the assertion for status code in this test. But I have retained it because if something goes wrong in the Amend assertions I want to make sure that the Project was actually created before I try to amend it.
I don’t want to re-use data from the Create test, and nor do I want to make tests dependent upon each other.
Also the abstraction layer means that repeating these assertions does not take up much code and doesn’t add to the maintenance overhead of the test.
This is the test that uses the ‘time based wait” synchronisation.
@Test
public void aUserCanAmendAProjectName(){
TracksApi api = new TracksApi(TestEnvDefaults.getTestEnv()); // create a new project
String newProjectName =
"A New Project" +
new RandomDataGenerator().randomWord(); api.createProject(newProjectName);
Assert.assertEquals(201, api.getLastResponse().
getStatusCode()); String projectId = new TracksResponseProcessor(
api.getLastResponse()) .getIdFromLocation(); TracksProject createdProject = api.getProject(projectId); Assert.assertEquals(newProjectName, createdProject.getName()); Wait.aFewSeconds(2);
// so that when we compare update times they are different
// amend the project
Map<String,String> fieldsToAmend =
new HashMap<String,String>(); fieldsToAmend.put("name", "the new name " +
new RandomDataGenerator().randomWord()); api.amendProject(projectId, fieldsToAmend );
TracksProject amendedProject = api.getProject(projectId); // check amended date has changed
contentEquals(
createdProject.getUpdatedAt())); // check created is the same
Assert.assertTrue(amendedProject.getCreatedAt(). contentEquals(
createdProject.getCreatedAt())); // check name changed
Assert.assertEquals(fieldsToAmend.get("name"), amendedProject.getName()); }
Delete
Given the extremes that I went to in the Create test to make sure that the Project was in the list after being created. It seems that I was a little more lax with the Delete test since I only check that the system reports the Project as missing with a 404 rather
than getting the full list of Projects and checking through the list.
@Test
public void aUserCanDeleteAProject(){
TracksApi api = new TracksApi(TestEnvDefaults.getTestEnv()); api.createProject("A New Project" +
new RandomDataGenerator().randomWord()); Assert.assertEquals(201,
api.getLastResponse().getStatusCode()); String projectId = new TracksResponseProcessor(
api.getLastResponse()) .getIdFromLocation(); // check we can get it
api.getProject(projectId); Assert.assertEquals(200,
api.getLastResponse().getStatusCode()); // check we can delete it
api.deleteProject(projectId); Assert.assertEquals(200,
api.getLastResponse().getStatusCode()); // check it has been deleted
api.getProject(projectId); Assert.assertEquals(404,
api.getLastResponse().getStatusCode()); }
I suspect that if I encountered a bug in the system with deletes then I might expand the test to GET all the Projects and iterate through the list.
I would do this by extracting the foundProject loop in the Create test (via an extract
this test.
At the moment this test only covers the basic conditions.
Summary
These tests provide a useful opportunity for discussing how much we should assert, and how much we should test.
In theory, I could have combined the Create and the Amend test, since I have to create a Project before I can amend it. I could even have created, amended and then deleted in the same test and had a single test in this class.
One of the many guidelines around creating @Test methods is that “the @Test should
only have one assert”. Clearly I don’t do that. I think it is appropriate to assert on whatever you need to.
My guiding rule was more like “the @Test should only have one intent”. So I have a
‘Create’ test, an ‘Amend’ test and a ‘Delete’ test. I don’t control the order that tests run in, so in theory, if a ‘create’ failed then all the tests would fail. But because I have a ‘Create’ test, I know that if it fails, then I would investigate that failure first. The ‘Create’ and ‘Amend’ both have fairly weak coverage. After identifying that a ‘Create’ works, I would normally have some data driven tests for covering more of the create conditions e.g. different lengths of name, different characters in names, can’t create with no name, etc.
Similarly the ‘Amend’ test is crying out for more coverage.
But, these tests are to demonstrate mechanisms, rather than serve as good examples of effective condition coverage.