Search code examples
javaspringdependency-injectionmockitohexagonal-architecture

Java integration test with fake outbound call


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 :

class diagram

We expose an HTTP endpoint to get customers and we call another app to get them.

  • In the domain : customers are represented by the Customer class.
  • In the externalapp layer : customers are represented by the CustomerModel class.
  • In the rest layer : customers are represented by the CustomerDto class.

Thus :

  • The CustomerSupplierAdapter class gets data from CustomerRepository and does the mapping from CustomerModel to Customer.
  • The 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 ?


Solution

  • 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.