Unit
Testing
Agenda
• Mocking With @MockBean
• Integration Testing With @DataJpaTest
• Unit Testing With @WebMvcTest
• Integration Testing With @SpringBootTest
Maven dependencies in Spring
application
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
...
</dependencies>
Maven dependencies for testing
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>2.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
...
</dependencies>
@Entity
@Entity
@Table
public class ExampleModel {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String field;
public ExampleModel(){}
public ExampleModel(String field) {
this.field = field;
}
public String getField() {
return field;
}
}
@Repository, @Service
@Repository
public interface ExampleRepository extends JpaRepository<ExampleModel,
Long> {
public ExampleModel findByField(String field);
}
@Service
public class ExampleService {
@Autowired
private ExampleRepository exampleRepository;
public ExampleModel getModelByField(String searchField) {
return exampleRepository.findByField(searchField);
}
public List<ExampleModel> getAllModels(){
return exampleRepository.findAll();
}
}
@Controller, application
@RestController
public class ExampleController {
@Autowired
ExampleService exampleService;
@GetMapping("/models")
public List<ExampleModel> getAllModels() {
return exampleService.getAllModels();
}
}
@SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
Mocking With @MockBean
The more dependencies we add in a test, the more reasons a test has to fail.
Service layer code is dependent on Repository. However, to test the Service layer,
we do not need to know or care about how the persistence layer is implemented.
If we use a mock instead, we can mock all potential failures away.
Using mocks, we only have to “instantiate” one mock instead of a whole rat-tail of
objects the real object might need to be instantiated.
Mock`s allow move from a potentially complex, slow, and flaky integration test
towards a simple, fast, and reliable unit test.
@MockBean. Test example
@ExtendWith(SpringExtension.class)
public class ExampleWithMockTest {
@TestConfiguration
static class ExampleServiceTestConfiguration {
@Bean
public ExampleService exampleService() {
return new ExampleService();
}
}
@Autowired
private ExampleService exampleService;
@MockBean
private ExampleRepository exampleRepository;
@BeforeEach
public void setUp() {
ExampleModel model = new ExampleModel("Field");
Mockito.when(exampleRepository.findByField("Field")).thenReturn(model);
}
@Test
public void exampleTestWithMock() {
String searchField = "Field";
String actual = exampleRepository.findByField(searchField).getField();
Assert.assertEquals(actual, searchField);
}
}
Integration Testing With @DataJpaTest
In Spring boot applications, we can use @DataJpaTest annotation that focuses only
on JPA components. This annotation will disable full auto-configuration and instead
apply only configuration relevant to JPA tests.
By default, tests annotated with @DataJpaTest are transactional and roll back at the
end of each test.
By default, it scans for @Entity classes and configures Spring Data JPA repositories
annotated with @Repository annotation.
If an embedded database is available on the classpath, it configures one as well.
A nice feature is that these tests may also inject a TestEntityManager bean, which
provides an alternative to the standard JPA EntityManager that is specifically
designed for tests.
@DataJpaTest. Test example
@ExtendWith(SpringExtension.class)
@DataJpaTest
@ContextConfiguration(classes = ExampleApplication.class)
public class ExampleWithDataJpaTest {
@Autowired
TestEntityManager entityManager;
@Autowired
ExampleRepository exampleRepository;
@Test
public void exampleTest(){
String field = "Field";
ExampleModel model = new ExampleModel(field);
exampleRepository.save(model);
ExampleModel actual = exampleRepository.findByField(field);
Assert.assertEquals(actual.getField(), field);
}
}
Unit Testing With @WebMvcTest
@WebMvcTest also auto-configures MockMvc which offers a powerful way of easy
testing MVC controllers without starting a full HTTP server.
simple unit test will not cover the HTTP layer. So, we need to introduce
Spring to our test to do the HTTP magic for us. Thus, we’re building an integration
test that tests the integration between our controller code and the components
Spring provides for HTTP support.
An integration test with Spring fires up a Spring application context that contains all
the beans we need. This includes framework beans that are responsible for
listening to certain URLs, serializing and deserializing to and from JSON and
translating exceptions to HTTP. These beans will evaluate the annotations that
would be ignored by a simple unit test.
Spring Boot provides the @WebMvcTest annotation to fire up an application context
that contains only the beans needed for testing a web controller
@WebMvcTest . Test example
@ExtendWith(SpringExtension.class)
@WebMvcTest(ExampleController.class)
@ContextConfiguration(classes = ExampleApplication.class)
public class ExampleWebMvcTest {
@Autowired
private MockMvc mvc;
@MockBean
private ExampleService exampleService;
@Test
public void exampleTest() throws Exception {
ExampleModel model = new ExampleModel("field");
List<ExampleModel> models = Arrays.asList(model);
given(exampleService.getAllModels()).willReturn(models);
mvc.perform(get("/models")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].field", is(model.getField())));
}
}
Integration Testing
With @SpringBootTest
Spring Boot provides the @SpringBootTest annotation which we can use to create
an application context containing all the objects we need for all of the above test
types.
For tests that cover the whole Spring Boot application from incoming request to
database, or tests that cover certain parts of the application that are hard to set up
manually, we can and should use @SpringBootTest.
@SpringBootTest. Test example
@ExtendWith(SpringExtension.class)
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.MOCK,
classes = ExampleApplication.class)
@AutoConfigureMockMvc
public class ExampleSpringBootTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ExampleRepository repository;
@Test
public void exampleTest() throws Exception {
ExampleModel model1 = new ExampleModel("field");
repository.saveAndFlush(model1);
mockMvc.perform(get("/models")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content()
.contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$[0].field", is("field")));
}
}
References
https://www.baeldung.com/spring-boot-testing
https://reflectoring.io/unit-testing-spring-boot/
https://www.baeldung.com/java-spring-mockito-mock-mockbean
https://www.baeldung.com/mockito-mock-methods
https://junit.org/junit5/
https://junit.org/junit4/
https://testng.org/doc/
SoftServe Confidential