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.