How to inject mocks while testing classes using CDI in production

…衆ロ難τιáo~ 提交于 2019-12-10 12:41:33

问题


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?


回答1:


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);
    }

}



回答2:


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() {
         ....
    }

}


来源:https://stackoverflow.com/questions/32422930/how-to-inject-mocks-while-testing-classes-using-cdi-in-production

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!