0

Integration tests with spring, junit and testng

My former post “custom http status codes” about such a project on github was focussed on creating certain http status codes. Two not covered subjects are unit and integration tests. This post covers one approach how to create those tests using two popular test frameworks:

together with spring test.

The first test I added to the existing project was a unit test class based on testng:

...
@Test(groups = "a")
public void selectAll() throws Exception {
    Map<String,RESTItem> itemMap = imageDAO.selectAll();
    log.debug("itemMap: " + itemMap);
    String expected =
           "{id1234=RESTItem{externalID=id1234,
                             description=description_2,
                             label=another label},
             id123=RESTItem{externalID=id123,
                            description=description,
                            label=label}}";
    Assert.assertEquals(itemMap.toString().trim(), expected.trim());
}
...
@Test(groups = "b", dependsOnGroups = "a")
public void updateByExternalID() throws Exception {
    boolean result = imageDAO.updateItemByExternalID(
    new RESTItem("id123", "description_changed", "label_changed"));
    log.debug("result: " + result);
    RESTItem RESTItem = imageDAO.getItemByExternalId("id123");
    log.debug("RESTItem: " + RESTItem);
    String expected = "RESTItem{externalID=id123,
                                description=description_changed,
                                label=label_changed}";
    Assert.assertEquals(RESTItem.toString().trim(), expected.trim());
}
...

These unit tests should also run on webservice level. As the project is based on spring, I went for spring test to test on integration level as well:

...
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations =
  {"classpath:**/*exceptionDispatcher-servlet.xml"})
@WebAppConfiguration
public class ItemServiceIT {
...
@Test
public void testItems() throws Exception {
 mockMvc.perform(get(basePath).accept(MediaType.APPLICATION_JSON))
  //.andDo(print())
  .andExpect(status().isOk())
  .andExpect(content().contentType(MediaType.APPLICATION_JSON))
  .andExpect(content().string(
    "[{\"description\":\"description_2\",
      \"label\":\"another label\",
      \"externalID\":\"id1234\"}," +
    "{\"description\":\"description\",
      \"label\":\"label\",\"externalID\":\"id123\"}]"))
  .andExpect(jsonPath("$..description[0]").value("description_2"))
  .andExpect(jsonPath("$..label[0]").value("another label"))
  .andExpect(jsonPath("$..externalID[0]").value("id1234"))
  .andExpect(jsonPath("$..description[1]").value("description"))
  .andExpect(jsonPath("$..label[1]").value("label"))
  .andExpect(jsonPath("$..externalID[1]").value("id123"))
    ;
}
...
@Test
public void testUpdateExistingResource() throws Exception {
    String itemID = "id123";

    String newDescription = "new description XXX";
    String newLabel = "new_label";

    String existingDescription = "description";
    String existingLabel = "label";

    mockMvc.perform(
     post(basePath + "{itemID}", itemID).
      contentType(MediaType.APPLICATION_JSON)
       .content("{\"label\":\"" + newLabel + "\",
         \"description\":\"" + newDescription + "\"}"))
       //.andDo(print())
      .andExpect(status().isOk())
    ;

    this.testGet(itemID, descriptionLabel,
      newDescription, labelLabel, newLabel, "externalID");

    mockMvc.perform(
     post(basePath + "{itemID}", itemID)
      .contentType(MediaType.APPLICATION_JSON)
      .content("{\"label\":\"" + existingLabel + "\",
        \"description\":\"" + existingDescription + "\"}"))
      //.andDo(print())
      .andExpect(status().isOk())
    ;

    this.testGet(itemID, descriptionLabel,
     existingDescription, labelLabel, existingLabel, "externalID");
}
...
private void testGet(String itemID, String descriptionLabel,
 String description, String labelLabel,
 String label, String externalIDLabel) throws Exception {
  mockMvc.perform(get(basePath + "{itemID}", itemID)
   .accept(MediaType.APPLICATION_JSON))
  //.andDo(print())
  .andExpect(status().isOk())
  .andExpect(content().contentType(MediaType.APPLICATION_JSON))
  .andExpect(content().string(
   "{\"" + descriptionLabel + "\":\"" + description + "\",\"" +
    labelLabel + "\":\"" + label + "\",\"" + externalIDLabel +
    "\":\"" + itemID + "\"}"))
  .andExpect(MockMvcResultMatchers.jsonPath("$.description")
   .value(description))
  .andExpect(MockMvcResultMatchers.jsonPath("$.label")
   .value(label))
  .andExpect(MockMvcResultMatchers.jsonPath("$.externalID")
   .value(itemID))
  ;
}

@Configuration
public static class TestConfiguration {

    @Bean
    public ItemController itemController() {
        return new ItemController();
    }

}

There are comparable methods in the integration test class as above in the unit test class. The test methods show two general options how to check the response body.

  • You can check the response string as is:
    content.string("...")
    
  • The other option checks certain elements of the response:
    MockMvcResultMatchers.jsonPath("$.description")
     .value(description)
    

Please note: the controller configuration at the end of the code sample is important. Without that configuration the tests won’t run, because the configured controller controls the webservice resources to test.

As I started with testNG tests and had to switch to junit, I wondered if spring integration tests run with testNG as well. I decided to create a new branch for tests based on testNG. This refers not only to integration tests. I also “refactored” the unit tests back to testNG ( as it was before ) . This is a code snippet of the integration class:

@Test
@ContextConfiguration(locations =
  {"classpath:**/*exceptionDispatcher-servlet.xml"})
@WebAppConfiguration
public class ItemServiceIT extends AbstractTestNGSpringContextTests {
...
@Test(groups = "a")
public void testItems() throws Exception {
 mockMvc.perform(get(basePath).accept(MediaType.APPLICATION_JSON))
  //.andDo(print())
  .andExpect(status().isOk())
  .andExpect(content().contentType(MediaType.APPLICATION_JSON))
  .andExpect(content().string("[{\"description\":\"description_2\",
    \"label\":\"another label\",\"externalID\":\"id1234\"}," +
   "{\"description\":\"description\",\"label\":\"label\",\"externalID\":\"id123\"}]"))
  .andExpect(jsonPath("$..description[0]").value("description_2"))
  .andExpect(jsonPath("$..label[0]").value("another label"))
  .andExpect(jsonPath("$..externalID[0]").value("id1234"))
  .andExpect(jsonPath("$..description[1]").value("description"))
  .andExpect(jsonPath("$..label[1]").value("label"))
  .andExpect(jsonPath("$..externalID[1]").value("id123"))
 ;
}
...
@Test(groups = "b", dependsOnGroups = "a")
public void testUpdateExistingResource() throws Exception {
 String itemID = "id123";

 String newDescription = "new description XXX";
 String newLabel = "new_label";

 String existingDescription = "description";
 String existingLabel = "label";

 mockMvc.perform(post(basePath + "{itemID}", itemID)
  .contentType(MediaType.APPLICATION_JSON)
  .content("{\"label\":\"" + newLabel + "\",\"description\":\""
    + newDescription + "\"}"))
  //.andDo(print())
  .andExpect(status().isOk())
 ;

 this.testGet(itemID, descriptionLabel, newDescription,
   labelLabel, newLabel, "externalID");

 mockMvc.perform(post(basePath + "{itemID}", itemID)
  .contentType(MediaType.APPLICATION_JSON)
  .content("{\"label\":\"" + existingLabel + "\",\"description\":\""
     + existingDescription + "\"}"))
  //.andDo(print())
  .andExpect(status().isOk())
 ;

 this.testGet(itemID, descriptionLabel, existingDescription,
  labelLabel, existingLabel, "externalID");
}
...
private void testGet(String itemID,
 String descriptionLabel, String description, String labelLabel,
 String label, String externalIDLabel) throws Exception {
  mockMvc.perform(get(basePath + "{itemID}", itemID)
   .accept(MediaType.APPLICATION_JSON))
  //.andDo(print())
  .andExpect(status().isOk())
  .andExpect(content().contentType(MediaType.APPLICATION_JSON))
  .andExpect(content().string("{\"" + descriptionLabel + "\":\""
     + description + "\",\"" +
    labelLabel + "\":\"" + label + "\",\"" + externalIDLabel
     + "\":\"" + itemID + "\"}"))
  .andExpect(MockMvcResultMatchers
   .jsonPath("$.description").value(description))
  .andExpect(MockMvcResultMatchers
   .jsonPath("$.label").value(label))
  .andExpect(MockMvcResultMatchers
   .jsonPath("$.externalID").value(itemID))
 ;
}

@Configuration
public static class TestConfiguration {

    @Bean
    public ItemController itemController() {
        return new ItemController();
    }

}

}

There are two main differences, compared to the integration tests based on junit

  • instead of annotating the class with @RunWith, the class extends AbstractTestNGSpringContextTests
  • the testng @Tests annotation can have parameters like (groups = “b”, dependsOnGroups = “a”) to control the execution order of tests

At this state the implementation was not complete yet. In case the controller returns a 303 see other, the relocation url is not tested yet. This is done with 0.4.2 tag which is again based on junit.
There is also a regarding integration test method testSeeOther based on testng.
So far, some unit tests depend on other finished unit tests. Having independent tests is a better approach. These annotations:

  • testng
    • @BeforeMethod
    • @AfterMethod
  • junit
    • @Before
    • @After

can help to decouple the tests.

Methods annotated with @BeforeMethod/@Before run before every test method, methods annotated with @AfterMethod/@After run after every test method. So there are 2 flavours of class ItemDaoImplTest.java:

with independent tests.

Please find the complete source code including all details on github: customHTTPcode.

Lothar Schulz

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.