Implementation

Imagine the whole manufacture car use case shown in the previous example in the Unit tests section, needs to be tested. A car is created, using a delegate CarFactory, and then is persisted into the database. Testing the persistence layer is out of this test scope, therefore the entity manager will be mocked away.

The following code snippet shows the component test to the manufacture car use case:

public class ManufactureCarTest {

    private CarManufacturer carManufacturer;

    @Before
    public void setUp() {
        carManufacturer = new CarManufacturer();
        carManufacturer.carFactory = new CarFactory();
        carManufacturer.entityManager = mock(EntityManager.class);
    }

    @Test
    public void test() {
        when(carManufacturer.entityManager.merge(any())).then(a -> a.getArgument(0));

        Specification spec = ...
        Car expected = ...

        assertThat(carManufacturer.manufactureCar(spec)).isEqualTo(expected);
        verify(carManufacturer.entityManager).merge(any(Car.class));
    }
}

The preceding example is quite similar to the previous ones, with the exception that CarFactory is instantiated, using the actual business logic. The mocks, which represent the boundaries of the test case, verify correct behavior.

However, while this approach works for straightforward use cases, it is somewhat naive in regard to more sophisticated real-world scenarios. The boundaries of the test case are as seen in the test class, for the CarFactory delegate to be self-sufficient and not inject further controls. Of course, all interdependent units that are part of a component test can define delegates. Depending on the nature of the test and the use case, these nested delegates also need to be instantiated or mocked away.

This will eventually lead to a lot of effort required in setting up the test case. We could make use of test framework functionality such as Mockito annotations here. Doing so, the test case injects all classes that are involved in the test case. Developers specify which of them will be instantiated or mocked away, respectively. Mockito provides functionality to resolve references, which is sufficient for the majority of use cases.

The following code snippet shows a component test of a similar scenario, this time using a CarFactory delegate that has an AssemblyLine and Automation as nested dependencies. These are mocked away in the test case:

@RunWith(MockitoJUnitRunner.class)
public class ManufactureCarTest {

    @InjectMocks
    private CarManufacturer carManufacturer;

    @InjectMocks
    private CarFactory carFactory;

    @Mock
    private EntityManager entityManager;

    @Mock
    private AssemblyLine assemblyLine;

    @Mock
    private Automation automation;

    @Before
    public void setUp() {
        carManufacturer.carFactory = carFactory;

        // setup required mock behavior such as ...
        when(assemblyLine.assemble()).thenReturn(...);
    }

    @Test
    public void test() {
        Specification spec = ...
        Car expected = ...

        assertThat(carManufacturer.manufactureCar(spec)).isEqualTo(expected);
        verify(carManufacturer.entityManager).merge(any(Car.class));
    }
}

The @InjectMocks functionality of Mockito attempts to resolve object references with mock objects injected as @Mock in the test case. The references are set using reflection. If boundaries or controls define new delegates, they need to be defined at least as a @Mock object in the test cases to prevent NullPointerException. However, this approach only partially improves the situation since it leads to a lot of dependencies being defined in the test class.

An enterprise project with a growing number of component tests introduces a lot of verbosity and duplication if it follows only this approach.

To make the test code less verbose and restrict this duplication, we could introduce a test superclass for a specific use case scenario. That superclass would contain all @Mock and @InjectMock definitions, setting up required dependencies, delegates, and mocks. However, such test superclasses also contain a lot of implicit logic, which delegates are defined and being used somewhere in the extended test cases. This approach leads to test cases that are tightly coupled to commonly used superclasses, eventually leading to implicitly coupling the test cases.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.217.198