Unit testing a class with autowired notation using

2019-03-09 21:57发布

问题:

I am trying to write a Unit test for a class that has several of its fields marked @Autowired. Given the fact that Spring is automatically resolving the concrete implementations for these fields, I am having a hard time figuring out how to plug my Mock objects(created via EasyMock) as the dependencies during the test-run. Using @Autowired in the class means lack of setters in that class. Is there a way for me to plug my mock objects in without creating additional setters in class?

Here's an example of what I am trying to accomplish:

public class SomeClassUnderTest implements SomeOtherClass{

@Autowired
private SomeType someType;

@Autowired
private SomeOtherType someOtherType;

@Override
public SomeReturnType someMethodIWouldLikeToTest(){
//Uses someType and someOtherType and returns SomeReturnType
}

}

Here's how I am crafting my Test class before I hit the wall:

public class MyTestClassForSomeClassUnderTest{
  private SomeType someType;
  private SomeOtherType someOtherType;

  @Before
  public void testSetUp(){
    SomeClassUnderTest someClassToTest = new SomeClassUnderTest();
    someType = EasyMock.createMock(SomeType.class);
    someOtherType = EasyMock.createMock(SomeOtherType.class);
    //How to set dependencies????
  }

  @Test
  public void TestSomeMethodIWouldLikeToTest(){
    //??????
  } 

}

It will be great to get a push in the right direction.

Thanks

回答1:

You can reflectively inject dependencies directly into the field using ReflectionTestUtils, e.g.

ReflectionTestUtils.setField( testInstance, "fieldName", fieldValue );

Some would argue that it's preferable to add a package-visible setter method to the class anyway, used solely by the tests. Alternatively, use autowired constructors, rather than autowired fields, and inject the test dependencies into that.



回答2:

Although it is possible to set these fields via reflection, doing so will prevent your development tools from finding usages of these fields, and make it harder for you to refactor SomeClassToTest in the future.

It would be better to add public setters for these fields, and place the @Autowired annotations on these instead. Not only does this avoid reflection, but it also clarifies the external interface of the class and ensures that your unit test uses just this interface. I see that SomeClassToTest already implements the SomeOtherClass interface, and I assume that clients of SomeClassToTest only use this interface, so there is little danger in making the setters on SomeClassToTest public.

Better still, use constructor injection and make the fields final. You can still use @Autowired on the constructor arguments.



回答3:

I do not recommend the answer which had been accepted, i.e. using reflection on your own (without a mocking framework).

Since the version 3.2 of EasyMock you can use annotations in order to define the mocks and inject them into the class under test. A full description how to do it can be found in the official documentation of EasyMock: http://easymock.org/user-guide.html#mocking-annotations

Here's an example from the above-mentioned site:

import static org.easymock.EasyMock.*;
import org.easymock.EasyMockRunner;
import org.easymock.TestSubject;
import org.easymock.Mock;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(EasyMockRunner.class)
public class ExampleTest {

  @TestSubject
  private ClassUnderTest classUnderTest = new ClassUnderTest(); // 2

  @Mock
  private Collaborator mock; // 1

  @Test
  public void testRemoveNonExistingDocument() {
    replay(mock);
    classUnderTest.removeDocument("Does not exist");
  }
}