简体   繁体   中英

How can I pass an object to my class under test in a UnitTest class?

I want to test a class that has an "ObjectMapper" as an instance variable:

public class EventTransformerImpl implements EventTransformer {


 @Inject
 private ObjectMapper objectMapper;

private String convertParametersToJson(NotificationConfig config) {
        String parametersAsJson = null;
        try {
            parametersAsJson = objectMapper.writeValueAsString(config.getOrdinaryParameters());
        } catch (IOException e) {
            logger.info("There was a problem in writing json:" + config.getParameters(), e);
            parametersAsJson = null;
        }
        return parametersAsJson;
    }
}

this class does not have any "setter" or "constructor" for initializing "objectMapper" (it is initialized using "spring"):

<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
    <property name="dateFormat">
        <bean class="java.text.SimpleDateFormat">
            <constructor-arg value="yyyy-MM-dd'T'HH:mm:ssZ"/>
        </bean>
    </property>
</bean>

when I want to test "EventTransformerImpl" class, objectMapper has null value, how can I pass "objectMapper" to "EventTransformerImpl" in my unit test class.

This is a typical example where a mock framework, such as Mockito , can help you:

public class TestClass {
  @Mock
  private ObjectMapper objectMapper;

  @InjectMocks
  private EventTransformer eventTransformer;

  @BeforeMethod
  public void setUp() {
    eventTransformer = new EventTransformerImpl();
    MockitoAnnotations.initMocks(this);
  }  
}

Here, @Mock and @InjectMocks are parts of the Mockito framework. The magic happens in MockitoAnnotations.initMocks(this) which will scan this , which in this case is TestClass , for these annotations. Mockito initializes objectMapper as a mock, and injects it into eventTransformer . You can then use Mockito to decide how objectMapper should behave.

You can read more about Mockito here .

Also, @BeforeMethod is a TestNG method, analogue with JUnits @Before .


However, many people prefer constructor injection , as proposed by davidxxx . This will make it clearer which dependencies EventTransformerImpl has, and forces it to be initialized with the correct dependencies. By doing this, there is no need to "magically" (I guess Mockito uses reflection under the hood) inject the dependencies, simply initialize the class to be tested by calling the constructor.

Your class could look something like this (here using Spring's @Autowired annotation):

public class EventTransformerImpl implements EventTransformer {

  private ObjectMapper objectMapper;

  @Autowired
  public EventTransformerImpl(ObjectMapper objectMapper) {
    this.objectMapper = objectMapper;
  }

  private String convertParametersToJson(NotificationConfig config) {
    String parametersAsJson = null;
    try {
      parametersAsJson = objectMapper.writeValueAsString(config.getOrdinaryParameters());
    } catch (IOException e) {
      logger.info("There was a problem in writing json:" + config.getParameters(), e);
      parametersAsJson = null;
    }
    return parametersAsJson;
  }
}

This will be less error prone, because with @InjectMocks , Mockito will silently ignore the injection if it fails, while calling a constructor gives the test class full control of the initialization of the test.

1) A class should be testable naturally : why not adding a constructor in EventTransformerImpl to inject the mapper ?

You could do something like that in the spring configuration :

  <bean id="EventTransformer" class="EventTransformerImpl">
      <constructor-arg ref="objectMapper"/>
   </bean>

And in the test you could instantiate EventTransformerImpl with a ObjectMapper mocked dependency in the constructor.

2) The other solution is using reflection to inject the field in the class under test in the test method.

You could use the setField() method of ReflectionTestUtils class of Spring to set the dependency or if you didn't use Spring you could have writen a simple utility method to do the job :

public static final void injectValue(String fieldName, Object fieldValue, Object targetObject) {
    try {
        Field f = targetObject.getClass().getDeclaredField(fieldName);
        f.setAccessible(true);
        f.set(targetObject, fieldValue);
    } catch (Exception e) {
       // handle the exception
    }
}

For injecting Spring dependencies in JUnit tests and other objects they depend on I use

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:testBeans.xml" })
public class TestRunner {

    @Test
    public void test1() {

You can provide in-code bean initialization as well

@RunWith(SpringJUnit4ClassRunner.class)
@Configuration

Read more at https://www.mkyong.com/unittest/junit-spring-integration-example/

All three answers are good; so I don't need to repeat the details of those, but I think it is worth to "align" on the "complete" picture.

As shown Spring has built-in means to do injection for unit tests; and you could just go forward and use that. But of course, that means that you are even more "coupling" your business logic code to the Spring framework.

So, if you ever consider re-using your code in a non-Spring situation, then you will find that not only your production code, but also your unit tests will require major rework in order to be used without Spring.

Thus there is a certain merit in the two other answers that suggest to change your production code to enable you to at least fully test your production code without adding a dependency to Spring here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM