Unit and Integration tests in Spring Boot
Spring Boot is an awesome project that aims to make it easy creating production ready spring powered applications, bringing a convention over configuration based setup. It makes it easier to start a project with a default setup that can be customised as you need.
Today I'd like to share with you how to write different types of tests using Spring Boot. The idea is show how you can write test using Spring Boot easily (specially after the 1.4 release).
Unit Tests
I'm sure that you have a good understanding on unit tests so I'll keep it to the basics. Unit tests are responsible for testing a specific piece of code, just a small functionality (unit) of the code. These are the tests that we want to run as fast as we can as the developer will run these tests a lot of times during the development. If you are using TDD you'll probably run it even more!
Stay out of Spring as much as you can
A good practice when working with Spring (or with JEE) is to isolate your business logic from specific Spring functionalities. Everyone want to keep their code loosely coupled and with high cohesion. When you need to mock lots of dependencies to unit test a specific thing that is a sign of high coupling. If you caught yourself in this situation, maybe it's a good idea to stop and think about separating some concerns into new classes.
Take a look into the CreateClientService
class. You'll notice that it has a single dependency on the ClientRepository
class. Now take a look into the CreateClientServiceTest
and notice how I can mock the dependencies of my CreateClientService
easily and execute simple unit tests using zero Spring functionality.
public class CreateClientServiceTest {
private CreateClientService createClientService;
private ClientRepository clientRepositoryMock;
@Before
public void setUp() {
clientRepositoryMock = Mockito.mock(ClientRepository.class);
createClientService = new CreateClientService(clientRepositoryMock);
}
@Test
public void createClientSuccessfuly() throws Exception {
when(clientRepositoryMock.findByName(eq("Foo"))).thenReturn(Optional.empty());
doAnswer(returnsFirstArg()).when(clientRepositoryMock).save(any(Client.class));
Client client = createClientService.createClient("Foo");
assertEquals("Foo", client.getName());
assertNotNull(client.getNumber());
}
@Test(expected = InvalidClientNameException.class)
public void createClientWithEmptyName() throws Exception {
createClientService.createClient("");
}
@Test(expected = ClientNameAlreadyExistsException.class)
public void createClientWithExistingName() throws Exception {
doThrow(new ClientNameAlreadyExistsException()).when(clientRepositoryMock).findByName(eq("Foo"));
createClientService.createClient("Foo");
}
}
Another detail that is important is how we can use constructor injection as a semantic way of declaring the dependencies of an object. When we use constructor injection we are making explicit all required dependencies of that object. Based on the constructor is clear that an instance of CreateClientService
can't exist without an instance of ClientRepository
.
When do I need Spring for testing?
Sometimes, you'll need to do some unit tests relying on Spring framework. For example, if you have a repository that has a custom query using the @Query
annotation, you might need to test your query. Also, if you are serialising/deserialising objects, you'd want to make sure that your object mapping is working. You might want to test your controllers as well, when you have some parameter validation or error handling. How can you be sure that you are using Spring correctly? In these situations you can take advantage of the new Spring Boot's test annotations.
Testing Spring Data repositories
If you look into the ClientRepositoryTest
you'll see that we are using the annotation @DataJpaTest
. Using this annotation we can have a TestEntityManager
injected on the test, and use it to change the database to a state in which it's possible to unit test our repository methods. In the code example, I'm testing a simple findByName()
method (that is implemented by Spring and I don't really would test in a real life application). I'm using the injected entity manager to create a Client
, and then I'm retrieving the created client using the repository.
@RunWith(SpringRunner.class)
@DataJpaTest
public class ClientRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private ClientRepository clientRepository;
@Test
public void testFindByName() {
entityManager.persist(new Client("Foo"));
Optional<Client> client = clientRepository.findByName("Foo");
assertEquals("Foo", client.get().getName());
}
}
This kind of test is useful either when you have custom implemented repositories or when you are using @Query
annotation to specify the queries fired agains the database.
Testing Spring MVC controllers
How many times have you made a mistake and mapped a wrong parameter in the controller? When was the last time that you tested all your bean validation to make sure that you weren't missing anything? With Spring Boot 1.4, now it's even simpler testing these controller specific responsibilities, you just need to use the @WebMvcTest
annotation.
Take a look into the ClientControllerTest
test class. I'm using @WebMvcTest(ClientController.class)
to declare that this is a unit test for the ClientController
class. Also, notice that I can use the @MockBean
annotation to easily create a Mockito mock of my dependency CreateClientService
class, and that mock will be injected automatically into my controller. Using these annotations you can mock your controller dependencies easily, and create isolated test that will be concerned only about your controller responsibilities.
Another good technique for unit testing controllers is to use a MockMvc
instance. It's possible to dispatch requests to your controllers that will be processed as they will be in the web server. This makes test bean validations and url/parameters mappings easier.
@RunWith(SpringRunner.class)
public class ClientControllerTest {
@Autowired
MockMvc mockMvc;
@MockBean
CreateClientService createClientServiceMock;
@Autowired
ObjectMapper objectMapper;
@Test
public void testCreateClientSuccessfully() throws Exception {
given(createClientServiceMock.createClient("Foo")).willReturn(new Client("Foo"));
mockMvc.perform(post("/clients")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(new CreateClientRequest("Foo"))))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name", is("Foo")))
.andExpect(jsonPath("$.number", notNullValue()));
}
...
}
Integration Tests
Spring Boot offers a lot of support for writing integration tests for your application. First of all, you can use the new @SpringBootTest
annotation. This annotation configures a complete test environment with all your beans and everything set up. You can choose to start a mock servlet environment, or even start a real one with the webEnvironment
attribute of the @SpringBootTest
annotation.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CreateClientIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void createClient() {
ResponseEntity<Client> responseEntity =
restTemplate.postForEntity("/clients", new CreateClientRequest("Foo"), Client.class);
Client client = responseEntity.getBody();
assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
assertEquals("Foo", client.getName());
}
}
In the example code above, we are starting a full Spring container with default configuration. The TestRestTemplate
is configured automatically when you use the @SpringBootTest
annotation, and is configured to resolve relative paths to http://localhost:${local.server.port}
.
When we execute the createClient()
test method, we'll send a POST to the web server that will be routed to the ClientController
. After that, the controller will call the CreateClientService
to create the client. In the end, the service will use the ClientRepository
to create the client in the database. We are testing our full application stack with a few configurations.
Conclusion
I hope that after reading this post you'll have a starting point for writing tests with Spring Boot. When writing a microservice, you want to be fast, and nothing is better than having a lot of test configurations and setup done by the framework, and not by you. This is just one of the things that makes me believe that currently, Spring Boot is the best choice for writing microservices in the Java ecosystem. Don't forget to read its documentation, it has a lot about testing with Spring Boot, what I've showed in this post is just the tip of the iceberg!