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:

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:

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:

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.