Testing the Spring MVC layer

[Index of Spring blogs]

Use “spring-boot-starter-test” to add all dependencies:

    org.springframework.boot
    spring-boot-starter-test
    test

You can annotate the test class with :

  • (nothing)
  • @SpringBootTest(webEnvironment = MOCK / RANDOM_PORT / DEFINED_PORT)
  • @AutoConfigureMockMvc
  • @WebMvcTest

To make rest call’s to the controllers, you can use one of the following:

  • RestTemplate
  • TestRestTemplate
  • RestAssuredMockMvc
  • MockMvc
  • WebTestClient (only when using webflux)

@SpringBootTest will load your complete Spring application, but without the Servlet container when specifying (webEnvironment = MOCK).
When using webEnvironment = RANDOM_PORT, a random port is used (which you can find in the test using @LocalServerPort).
It is also possible to autowire a TestRestTemplate, RestAssuredMockMvc or MockMvc which you can use to test the application using relative url’s.

@MockBean can be used to mock any bean in the application environment.

To test the serialization and deserialization of a JSON object, you can use @JsonTest

To test a web client, you can use @RestClientTest

Examples of using the different annotations and rest clients can be found below:

/*
 - Load complete spring environment but without the Servlet container
 - You can mock any bean in the system, using @MockBean
 - You cannot create real rest call's, but you can use MockMvc to make requests
 */
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = MOCK)
@AutoConfigureMockMvc  // this is needed to autowire the MockMvc
public class TestWithMockMvc {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TimeService timeService;

    @Test
    public void test_get_time() throws Exception {
        given(timeService.getTimeAsString()).willReturn(new TimeDto("11:22:33"));

        mockMvc.perform(get("/gettime")).andExpect(content().json("{\"time\": \"11:22:33\"}"));
    }
}

 

/*
 - Load complete spring environment including the Servlet container
 - You can mock any bean in the system, using @MockBean
 - You can inject mockMvc to make requests to the application using relative URL's

 Note:
 when use MockMvc,it might be better te use WebEnvironment.MOCK
 or use @WebMvcTest
 */
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@AutoConfigureMockMvc  // this is needed to autowire the MockMvc
public class TestWithMockMvc2 {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TimeService timeService;

    @Test
    public void test_get_time() throws Exception {
        given(timeService.getTimeAsString()).willReturn(new TimeDto("11:22:33"));

        mockMvc.perform(get("/gettime")).andExpect(content().json("{\"time\": \"11:22:33\"}"));
    }
}

 

/*
 - Load only the web components of spring:
    - @Controller,
    - @ControllerAdvice,
    - @JsonComponent,
    - Converter/GenericConverter,
    - Filter,
    - WebMvcConfigurer
    - HandlerMethodArgumentResolver
 - It does NOT load:
    -  @Component,
     - @Service
     - @Repositor

 - The components that are not loaded needs to be mocked
 - You can use MockMvc to make requests using relative URL's

 If you are looking to load your full application configuration and use MockMVC,
 you should consider @SpringBootTest combined with @AutoConfigureMockMvc rather than this annotation.

 */
@RunWith(SpringRunner.class)
@WebMvcTest
public class TestWithMockMvc3 {

    @Autowired
    MockMvc mockMvc;

    @MockBean
    TimeService timeService;

    @Test
    public void testTime() throws Exception {
        given(timeService.getTimeAsString()).willReturn(new TimeDto("19:03"));
        mockMvc.perform(get("/gettime")).andExpect(content().json("{\"time\": \"19:03\"}"));
    }
}

 

/*
 Test a controller without loading the spring framework
 You can still use the mockMvc client for these tests
 */
public class TestWithMockMvc4 {

    private MockMvc mockMvc;

    private TimeService timeService = mock(TimeService.class);

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new Controller1(timeService)).build();
    }

    @Test
    public void test1() throws Exception {
        given(timeService.getTimeAsString()).willReturn(new TimeDto("19:03"));

        mockMvc.perform(get("/gettime")).andExpect(content().json("{\"time\": \"19:03\"}"));
    }
}

 

/*
 - Load complete spring environment, including the Servlet container
 - You can mock any bean in the system, using @MockBean
 - With the Autowired @WebApplicationContext, you can configure RestAssuredMockMvc
 - In your tests you can use RestAssuredMockMvc if you like to use the RestAssured syntax
 */
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class TestWithRestAssuredMockMvc {

    @Autowired
    WebApplicationContext context;

    @MockBean
    private TimeService timeService;

    @Before
    public void setup() {
        RestAssuredMockMvc.webAppContextSetup(context);
    }

    @Test
    public void test_using_localhost() {
        // given
        when(timeService.getTimeAsString()).thenReturn(new TimeDto("11:22:33"));

        // when:
        ResponseOptions response = given().get("/gettime");

        // then:
        DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
        assertThat(response.statusCode()).isEqualTo(200);
        assertThatJson(parsedJson).field("time").isEqualTo("11:22:33");
    }
}

 

/*
 - You can use RestAssured to test a controller without loading Spring
 */
public class TestWithRestAssuredMockMvc2 {

    private TimeService timeService = mock(TimeService.class);

    @Before
    public void setup() {
        RestAssuredMockMvc.standaloneSetup(new Controller1(timeService));
    }

    @Test
    public void test_using_localhost() {
        // given
        when(timeService.getTimeAsString()).thenReturn(new TimeDto("11:22:33"));

        // when:
        ResponseOptions response = given().get("/gettime");

        // then:
        DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
        assertThat(response.statusCode()).isEqualTo(200);
        assertThatJson(parsedJson).field("time").isEqualTo("11:22:33");

    }
}

 

/*
 - Load complete spring environment, including the Servlet container
 - You can mock any bean in the system, using @MockBean
 - With the Autowired @TestRestTemplate, you can make rest call's to the system with relative URL's

 - The TestRestTemplate is a wrapper around the RestTemplate
 */
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class TestWithRestTemplate {

    @LocalServerPort
    int randomServerPort;

    private RestTemplate restTemplate = new RestTemplate();

    @MockBean
    private TimeService timeService;

    @Test
    public void test_get_time() {
        // given
        given(timeService.getTimeAsString()).willReturn(new TimeDto("11:22:33"));

        // when
        String url = String.format("http://localhost:%d/gettime", randomServerPort);
        ResponseEntity exchange = restTemplate.exchange(url, HttpMethod.GET, null, TimeDto.class);

        // then
        assertThat(exchange.getStatusCodeValue()).isEqualTo(200);
        assertThat(exchange.getBody().time).isEqualTo("11:22:33");
    }
}

 

/*
 - Load complete spring environment, including the Servlet container
 - You can mock any bean in the system, using @MockBean
 - With the Autowired @TestRestTemplate, you can make rest call's to the system with relative URL's

 - The TestRestTemplate is a wrapper around the RestTemplate
 */
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class TestWithTestRestTemplate {

    @Autowired
    private TestRestTemplate restTemplate;

    @MockBean
    private TimeService timeService;

    @Test
    public void test_get_time() {
        // given
        given(timeService.getTimeAsString()).willReturn(new TimeDto("11:22:33"));

        // when
        ResponseEntity exchange = restTemplate.exchange("/gettime", HttpMethod.GET, null, TimeDto.class);

        // then
        assertThat(exchange.getStatusCodeValue()).isEqualTo(200);
        assertThat(exchange.getBody().time).isEqualTo("11:22:33");
    }
}

 

/*
  Test serialization and deserialization of a JSON object
 */
@RunWith(SpringRunner.class)
@JsonTest
public class SampleJsonTest {

    @Autowired
    private JacksonTester json;

    @Test
    public void testDeserialize() throws IOException {
        String jsonString = "{\"name\":\"Robbert\",\"age\":19}";
        Customer customer = json.parseObject(jsonString);
        assertThat(customer.getAge()).isEqualTo(19);
        assertThat(customer.getName()).isEqualTo("Robbert");
    }

    @Test
    public void testSerialize() throws IOException {
        Customer customer = new Customer("Robbert",19);
        // compare complete (exact) json
        assertThat(json.write(customer).getJson()).isEqualTo("{\"name\":\"Robbert\",\"age\":19}");

        // compare the content of the json, ignoring order of fields and whitespaces
        assertThat(json.write(customer)).isStrictlyEqualToJson("{\"age\" : 19, \"name\":\"Robbert\"}");

        // compare a string from the json
        assertThat(json.write(customer)).extractingJsonPathStringValue("name").isEqualTo("Robbert");

        // compare a number from the json
        assertThat(json.write(customer)).extractingJsonPathNumberValue("age").isEqualTo(19);
    }
}

 

/*
 Test a web client service that has the RestClient injected.
 The MockRestServiceServer will mock the responses that are expected from the RestClient
 */
@RunWith(SpringRunner.class)
@RestClientTest({TimeService.class})
@AutoConfigureWebClient(registerRestTemplate = true)
public class SampleRestClientTest {

    @Autowired
    TimeService timeService;

    @Autowired
    MockRestServiceServer server;

    @Test
    public void test1() {
        // given
        server
                .expect(requestTo("http://worldclockapi.com/api/json/utc/now"))
                .andRespond(
                        MockRestResponseCreators.withSuccess("{\"currentDateTime\":\"myDate\"}", MediaType.APPLICATION_JSON)
                );

        // when
        TimeDto timeAsString = timeService.getTimeAsString();

        // then
        assertThat(timeAsString).isEqualTo("myDate");
    }
}

 

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringwebfluxApplicationTests {

    @Autowired
    private WebTestClient webClient;

    @Test
    public void test_using_webclient() {
        webClient
                .get()
                .uri("/getpet")
                .exchange()
                .expectStatus()
                .isOk()
                .expectBody(Pet.class)
                .isEqualTo(new Pet("pet1", GENDER.MALE));
    }
}

All samples can be found here: https://github.com/robbertvdzon/spring-tests
and https://github.com/robbertvdzon/springfluxsamples/blob/master/src/test/java/com/example/springwebflux/SpringwebfluxApplicationTests.java

More documentation:

Testing using Spring boot: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html

Documentation: https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#spring-mvc-test-framework

Using @SpyBean: https://shekhargulati.com/2017/07/20/using-spring-boot-spybean/

 

2 gedachtes over “Testing the Spring MVC layer

Reacties zijn gesloten.