Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to inject mocks while testing classes using CDI in production

I am programming in a Java SE environment using WELD-SE for dependency injection. Therefore dependencies of a class look something like this:

public class ProductionCodeClass {
    @Inject
    private DependencyClass dependency;
}

When writing a unit test for this class I am creating a mock for DependencyClass and as I don't want to start a complete CDI environment for every test I run, I "inject" the mock manually:

import static TestSupport.setField;
import static org.mockito.Mockito.*;

public class ProductionCodeClassTest {
    @Before
    public void setUp() {
        mockedDependency = mock(DependencyClass.class);
        testedInstance = new ProductionCodeClass();
        setField(testedInstance, "dependency", mockedDependency);
    }
}

The statically imported method setField() I have written myself in a class with tools I use in testing:

public class TestSupport {
    public static void setField(
                                final Object instance,
                                final String field,
                                final Object value) {
        try {
            for (Class classIterator = instance.getClass();
                 classIterator != null;
                 classIterator = classIterator.getSuperclass()) {
                try {
                    final Field declaredField =
                                classIterator.getDeclaredField(field);
                    declaredField.setAccessible(true);
                    declaredField.set(instance, value);
                    return;
                } catch (final NoSuchFieldException nsfe) {
                    // ignored, we'll try the parent
                }
            }

            throw new NoSuchFieldException(
                      String.format(
                          "Field '%s' not found in %s",
                          field,
                          instance));
        } catch (final RuntimeException re) {
            throw re;
        } catch (final Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}

What I don't like about this solution is, that I need this helper over and over in any new project. I already packaged it as a Maven project I can add as a test dependency to my projects.

But isn't there something ready made in some other common library I am missing? Any comments on my way of doing this in general?

like image 609
Matthias Wimmer Avatar asked Sep 06 '15 11:09

Matthias Wimmer


People also ask

How do you mock and inject a mock?

Mockito @InjectMocks annotations allow us to inject mocked dependencies in the annotated class mocked object. This is useful when we have external dependencies in the class we want to mock. We can specify the mock objects to be injected using @Mock or @Spy annotations.

How does inject mock works?

@InjectMocks creates an instance of the class and injects the mocks that are created with the @Mock annotations into this instance. @Mock is used to create mocks that are needed to support the testing of the class to be tested. @InjectMocks is used to create class instances that need to be tested in the test class.


2 Answers

Alternative when mockitos build in functions do not suffice: Try needle4j.org

It's a injection/mock framework that allows injection of mocks and concrete instances and also supports postConstruct for lifecycle simulation.

 public class ProductionCodeClassTest {

    @Rule
    public final NeedleRule needle = new NeedleRule();

    // will create productionCodeClass and inject mocks by default
    @ObjectUnderTest(postConstruct=true)
    private ProductionCodeClass testedInstance;

    // this will automatically be a mock
    @Inject
    private AServiceProductionCodeClassDependsOn serviceMock;

    // this will be injected into ObjectUnderTest 
    @InjectIntoMany
    private ThisIsAnotherDependencyOfProdcutionCodeClass realObject = new ThisIsAnotherDependencyOfProdcutionCodeClass ();

    @Test
    public void test_stuff() {
         ....
    }

}
like image 41
Jan Galinski Avatar answered Sep 30 '22 16:09

Jan Galinski


Mockito supports this out of the box:

public class ProductionCodeClassTest {

    @Mock
    private DependencyClass dependency;

    @InjectMocks
    private ProductionCodeClass testedInstance;

    @Before
    public void setUp() {
        testedInstance = new ProductionCodeClass();
        MockitoAnnotations.initMocks(this);
    }

}

The @InjectMocks annotation will trigger injection of classes or interfaces mocked in the test class, in this case DependencyClass:

Mockito tries to inject by type (using name in case types are the same). Mockito does not throw anything when injection fails - you will have to satisfy the dependencies manually.

Here, I am also using the @Mock annotation instead of calling mock(). You could still use mock(), but I prefer using annotations.

As a side note, there are reflection tools available, which supports the functionality you implemented in TestSupport. One such example is ReflectionTestUtils.


Perhaps better still is to use constructor injection:

public class ProductionCodeClass {

    private final DependencyClass dependency;

    @Inject
    public ProductionCodeClass(DependencyClass dependency) {
        this.dependency = dependency;
    }
}

The main advantage here is that it is clear what classes the class depends on, and that it cannot easily be constructed without providing all the dependencies. Also, it allows the injected class to be final.

By doing this, @InjectMocks is not necessary. Instead, just create the class by providing the mock as a parameter to the constructor:

public class ProductionCodeClassTest {

    @Mock
    private DependencyClass dependency;

    private ProductionCodeClass testedInstance;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        testedInstance = new ProductionCodeClass(dependency);
    }

}
like image 182
Magnilex Avatar answered Sep 30 '22 16:09

Magnilex