Blue Vibes Test
We appreciate quality, therefore testing is very important to us. In order to make testing of BlueVibes application easy and fun, we have introduced the testing module.
1. How to start
The first step that should be done is to include dependency in pom:
<dependency>
<groupId>io.bluevibes</groupId>
<artifactId>bv-test</artifactId>
<scope>test</scope>
</dependency>
2. Features
The module comes with following features:
- core - utils and base annotations. Read about it here
- slicing context - unit testing of resource, service and repository with sliced context.
- security testing supports - allows adding user into test context.
2.1. JDBC Data repository component testing
Many developers do not test repository layer, with an excuse that it is testing of already tested library and there is nothing left to be tested. But it’s not so true and we suggest testing repository. These tests aim to prove that:
- developer did properly mapping entity-table
- configuration and annotation are correct(i.e. auditing, presetting id)
- in case of library upgrade, repository still working.
So it’s obvious that for testing repository is not enough to have only the repository in test context, beside that we need all jdbc-data, id-initialization, jdbc-auditor and repository beans. In order to achieve that and still have sliced context, BlueVibes offers annotation @JdbcRepositoryTest
. Since auditor is enabled too, testing of auditor could be done by adding @WithMockTokenUser
to test method.
Example:
@JdbcRepositoryTest
class ExampleRepositoryComponentTest {
@Autowired
private ExampleRepository repository;
@Test
void saveExampleStoresDataCorrectly() {
Example newExample = new Example();
//omitted initialization of example data
Example savedExample = repository.save(newExample);
Optional<Example> loaded = repository.findById(savedExample.getExampleId());
assertThat(loaded).isPresent();
//other assertions...
}
@Test
@WithMockTokenUser(username = "max")
void saveExampleStoresAuditData() {
Example newExample = new Example();
//omitted initialization of example data
Example savedExample = repository.save(newExample);
assertThat(savedExample.getCreatedBy())
.isEqualTo(TEST_USER);
//other assertions...
}
}
2.2. Service unit testing
Following the clean architecture principles, a service should contains only pure logic, and should not be contaminated with some third parts beans. It implicates that for testing of services, only the service bean is needed, everything else should be mocked. To load only the service bean into context, test class has to marked by @TestWithContext
.
Example:
@TestWithContext({ExampleService.class})
class ExampleServiceTest {
@Autowired
private ExampleService service;
@MockBean
private ExampleRepository repository;
@Test
void getExampleByIdThrowsExceptionWhenRepositoryReturnsOptionalEmpty() {
doReturn(Optional.empty())
.when(repository).findById(anyString());
assertThatThrownBy( () -> service.getExampleById("test"))
.isInsanceOf(EntityNotFoundException.class)
.hasMessage("Entity with id[test] not found in the system.");
}
}
Service layer contains most complex logic, so it would be most intensively tested. If you notice that your test of the service requires more beans not only the service bean or it requires to open some of private methods just for testing, it could be a code smell or design issue. So instead of including additional beans in test or change visibility of methods because of test, the component design should be reconsidered.
2.3. Resource testing
Usually implementation of a resource consists of endpoint mapping, service call and probably converting to dto. Just unit testing which will prove that resource method calls service and converter does not make sense, and will not prove that resource will work. So to prove that resource work and that declaration is correct we need sliced context, which beside Resource bean contains mvc beans, converter beans and etc. To create minimal context like this, BlueVibes offers two annotations:
@ResourceTest
- loads needed beans and auto-configuration for testing endpoints without security.@ResourceTestWithSecurity
- load same beans as previous annotation and additionally security beans and configuration
Example:
@ResourceTestWithSecurity
@TestWithContext({OtpResource.class})
class ExampleResourceTest {
@MockBean
private ExampleService service;
@Autowired
private MockMvc mvc;
@Test
@WithMockTokenUser
void getReturnsStatusForbiddenWhenUserIsNotAdmin() {
assertThat(mvc.perform(get("/api/example")))
.isForbidden();
}
@Test
@WithMockTokenUser(authorities = {"ADMIN"})
void getReturnsStatusOkAndExampleDataWhenUserIsAdminAndServiceReturnData() {
Example example = Example.builder()
.exampleId("test-id")
.name("Test")
.build();
assertThat(mvc.perform(get("/api/example")))
.isOk()
.hasPropertyWithValue("$.exampleId", example.getExampleId())
.hasPropertyWithValue("$.name", example.getName());
}
}
In example above, the first method we tested that endpoint security is correctly configured. In the second method we prove: that endpoint get mapping is correct, that correct service method returned and that object returned from service is converted to dto correctly. Additionally, we can do the testing of mapping ApplicationException
to 500, and EntityNotFoundException
to 404, but it’s not necessary to be done on every resource, because it configured by default. So these test methods prove that declaration of GET resource is correct.