Reputation: 165
I work on a Java project using Spring framework, JUnit and Mockito.
The application is in the middle of a chain with others application, so it exposes inbound ports (e.g. an HTTP API) to be called and uses outbound ports (e.g. web services and database) to call other apps.
I want to write something like an integration test that should pass through the whole java code from the inbound port to the outbound port, but without doing any call to anything that's outside of the project.
Let's take a very-simple-but-very-concrete example :
We expose an HTTP endpoint to get customers and we call another app to get them.
Customer
class.CustomerModel
class.CustomerDto
class.Thus :
CustomerSupplierAdapter
class gets data from CustomerRepository
and does the mapping from CustomerModel
to Customer
.CustomerControllerAdapter
class does the mapping from Customer
to CustomerDto
and returns the data.Now, I want to test my app by calling the CustomerControllerAdapter
's getCustomers()
, which will call the real service, which will call the real supplier, which will call a fake repository.
I wrote the following code :
@ExtendWith(SpringExtension.class)
class CustomerIntegrationTest {
@Mock
private CustomerRepository repository;
@InjectMocks
private CustomerControllerAdapter controller;
@BeforeAll
void setupAll() {
CustomerOutboundPort customerOutboundPort = new CustomerSupplierAdapter(repository);
CustomerInboundPort customerInboundPort = new CustomerService(customerOutboundPort);
controller = new CustomerControllerAdapter(customerInboundPort);
}
@Test
void bulkQuery() {
// Given
CustomerModel model = new CustomerModel();
model.setName("Arya Stark");
doReturn(List.of(model)).when(repository).getCustomers();
// When
List<CustomerDto> dtos = controller.getCustomers();
// Then
assertThat(dtos).hasSize(1);
assertThat(dtos.get(0).getName()).isEqualTo("Arya Stark");
}
}
But in this code, I do the "constructor's wiring" by myself in the setupAll()
instead of relying on Spring dependency injection. It is not a viable solution because it would be very hard to maintain in real-life context (controller may have multiple services, service may have multiple suppliers, etc).
Actually, I would like to have something like an annotation to set on a CustomerRepository instance to programmatically overload dependency injection. Like : "Hey Spring, if any @Service class needs a CustomerRepository then you should use this fake one instead of the usual concrete implementation" without having to do the wiring by myself.
Is there any way to achieve that using Spring, JUnit, Mockito or anything else ?
Upvotes: 1
Views: 812
Reputation: 1178
If you really want to replace every CustomerRepository in your tests (everywhere!) with a mock, I'd recommend going for a configuration which provides a @Bean, which creates a mocked bean.
@Profile("test")
@Configuration
public class TestConfiguration {
@Bean
@Primary
public CustomerRepository customerRepostiory() {
return Mockito.mock(CustomerRepository.class);
}
}
@MockBean can have negative effects on your test duration as it's quite possible Spring needs to restart it's context.
Alternatively, I'd recommend NOT mocking your repository at all, but instead using either an in memory equivalent (H2) or the TestContainers framework to start the real database for you. Instead of mocking, you insert data into your repository before you start your tests.
Upvotes: 1