Unit testing: Call #PostConstruct after defining mocked behaviour - java

I have two classes:
public MyService {
#Autowired
private MyDao myDao;
private List<Items> list;
#PostConstruct
private void init(){
list = myDao.getItems();
}
}
Now I'm wanting to involve MyService in a unit test, and so I'll mock the behaviour MyDao.
XML:
<bean class = "com.package.MyService">
<bean class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.package.MyDao"/>
</bean>
<util:list id="responseItems" value-type="com.package.Item">
<ref bean="item1"/>
<ref bean="item2"/>
</util:list>
Unit Test:
#ContextConfiguration("/test-context.xml")
#RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {
#Autowired
MyService myService
#Autowired
MyDao myDao;
#Resource
#Qualifier("responseItems")
private List<Item> responseItems;
#Before
public void setupTests() {
reset(myDao);
when(myDao.getItems()).thenReturn(responseItems);
}
}
The problem with this is that the MyService bean is created, and its #PostConstruct bean called before the mocked behaviour is defined.
How can I either define the mocked behaviour in the XML or delay #PostConstruct until after the unit test setup?

I have same kind of requirement in my project. where i need to set a string using #PostConstructor and I did not want to ran spring context or in other words I want simple mock. My requirement was follow:
public class MyService {
#Autowired
private SomeBean bean;
private String status;
#PostConstruct
private void init() {
status = someBean.getStatus();
}
}
Solution:
public class MyServiceTest(){
#InjectMocks
private MyService target;
#Mock
private SomeBean mockBean;
#Before
public void setUp() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
MockitoAnnotations.initMocks(this);
when(mockBean.getStatus()).thenReturn("http://test");
//call post-constructor
Method postConstruct = MyService.class.getDeclaredMethod("init",null); // methodName,parameters
postConstruct.setAccessible(true);
postConstruct.invoke(target);
}
}

MyDao sounds like it is an abstraction of an external system. Generally external systems shouldn't be called in #PostConstruct methods. Instead have your getItems() called by another method in MyService.
Mockito injections will take place after the Spring initiation at which point the mock isn't working as you see. You cannot delay the #PostConstruct. To beat this and have the load run automatically have MyService implement SmartLifecycle and call getItems() in start().

Related

Junit Mocked bean is not executing

I'm using spring boot rest API and want to test my service layer. In my service I have autowired few beans and its not through constructor. (I like it that way to keep it short).
In the junit, I have created mocks and for private field which I do want to execute, I have assigned using ReflectionTestUtils.setField. When I debug, the method inside the field is not getting executed which assigned by bean.
Service Class
#Component
public class MyService {
#Autowired
private MyRepository myRepository;
#Autowired
private ResponseMapper responseMapper;
public List<MyObj> getList(String param) throws MyException {
log.info("Getting details");
Optional<List<MyObj>> list = myRepository.findByParam(param);
List<MyObj> data = responseMapper.mapToResponseData(list);
return data;
}
}
Test Class
#RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {
#InjectMocks
private MyService myService;
#Mock
private MyRepository myRepository;
#Mock
private ResponseMapper responseMapper;
#Before
public void setUp() {
ReflectionTestUtils.setField(myService, "responseMapper", responseMapper);
}
#Test
public void getListTest() throws Exception {
when(myRepository.findByParam(anyString()))
.thenReturn(Optional.of(getSampleList()));
List<MyObj> list = myService.getList("param");
assertTrue(list.size() >0);
}
}
This results in Assertion failure and when I debug, the method mapToResponseData in ResponseMapper is not getting executed.
I know I can mock mapToResponseData method also. But I wanted it to execute so I don't have to write another test class for mapper alone.
What am I doing wrong? Is this the right wat to inject bean? And is using constructor in service to inject beans only option?

Can I inject mocks into a prototype bean with Autowired constructor?

Is it possible to inject a mock service into a prototype bean using the #Autowired constructor? I realize I could switch to setter injection but I would prefer to use the constructor if possible.
#Component
#Scope(value = "prototype")
public class Prototype {
private DependantService dependantService;
#Autowired
public Prototype(DependantService dependantService) {
this.dependantService = dependantService;
}
}
#SpringBootTest
public class TestPrototype {
#Autowired
private ApplicationContext ctx;
#Mock
private DependantService dependantService;
#Test
public void testPrototype() {
// How can I inject the mock service?
ctx.getBean(Prototype.class);
}
}
Turns out there is an overloaded version of the getBean method that accepts arguments. I would downvote my on question if I could.
#SpringBootTest
public class TestPrototype {
#Autowired
private ApplicationContext ctx;
#Mock
private DependantService dependantService;
#Test
public void testPrototype() {
Prototype p = ctx.getBean(Prototype.class, dependantService);
// Test p
}
}
If you want to speed up your unit tests, [and do true isolated unit testing,] I suggest taking a look at the #InjectMocks mockito annotation. #SpringBootTest fires up the Spring container which is pretty cumbersome.
#Controller
public class MyController {
#Inject
private Logger log;
public methodThatNeedsTesting(){
log.info("hey this was called");
}
}
#TestInstance(Lifecycle.PER_CLASS)
#ExtendWith({ MockitoExtension.class })
class MyControllerTest {
#Mock
private Logger log;
#InjectMocks
private MyController myController;
#Test
void test_methodThatNeedsTesting() throws Exception {
myController.methodThatNeedsTesting();
// myController will not throw an NPE above because the log field has been injected with a mock
}

Unit Test - Null mocked component is injected

I have the following use case:
I have a Test class with 3 components, from which 2 of them are inject into the third; I am using JUnit and Mockito for testing
public class MyTestClass{
#Mock
SomeService someService;
#Mock
AnotherService anotherService;
#InjectMock
MainService mainService;
#BeforeMethod
public void init() {
initMocks(this);
}
#Test
public void test(){
when(someService.someMethod(any())).thenReturn(something);
when(anotherService.someMethod(any()).thenReturn(something);
mainService.someMainMerhod();
// ...other assert logic
}
}
And here I have the MainService Spring component which has injected the two other components
#Component
public class MainService{
#Autowired
private SomeService someService; //Why here I have null component
private AnotherService anotherService; // and here I have an initialized component ???
public MainService(AnotherService anotherService){
this.anotherService = anotherService;
}
// implementation
}
Question 1 : Why someService instance is null when I am using both constructor and #Autowired?
Question 2 : Why if I am using only the constructor without #Autowired and vice versa, everything works, since I do not load the Spring context... I have unit tests...
The Javadoc states:
"Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection in order. If any of the strategy fail, then Mockito won’t report failure; i.e. you will have to provide dependencies yourself."
Hence it will fail silently.

How to AutoWire spring beans when using Mockito and Junit?

I am trying to set up my class to be used in Junit.
However when I try to do the below I get an error.
Current Test Class:
public class PersonServiceTest {
#Autowired
#InjectMocks
PersonService personService;
#Before
public void setUp() throws Exception
{
MockitoAnnotations.initMocks(this);
assertThat(PersonService, notNullValue());
}
//tests
Error:
org.mockito.exceptions.base.MockitoException:
Cannot instantiate #InjectMocks field named 'personService'
You haven't provided the instance at field declaration so I tried to construct the instance.
However the constructor or the initialization block threw an exception : null
How can I fix this?
You are not mocking anything in your code. #InjectMocks sets a class where a mock will be injected.
Your code should look like this
public class PersonServiceTest {
#InjectMocks
PersonService personService;
#Mock
MockedClass myMock;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
Mockito.doReturn("Whatever you want returned").when(myMock).mockMethod;
}
#Test()
public void testPerson() {
assertThat(personService.method, "what you expect");
}
Another solution is to use #ContextConfiguration annotation with static inner configuration class like so:
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class PersonServiceTest {
#Autowired
PersonService personService;
#Before
public void setUp() throws Exception {
when(personService.mockedMethod()).thenReturn("something to return");
}
#Test
public void testPerson() {
assertThat(personService.method(), "what you expect");
}
#Configuration
static class ContextConfiguration {
#Bean
public PersonService personService() {
return mock(PersonService.class);
}
}
}
Anyway, you need to mock something that the method you want to test uses inside to get desired behaviour of that method. It doesn't make sense to mock the service you're testing.
You're misunderstanding the purpose of the mock here.
When you mock out a class like this, you are pretending as if it's been injected into your application. That means you don't want to inject it!
The solution to this: set up whatever bean you were intending to inject as #Mock, and inject them into your test class via #InjectMocks.
It's unclear where the bean you want to inject is since all you have is the service defined, but...
#RunWith(MockitoJUnitRunner.class);
public class PersonServiceTest {
#Mock
private ExternalService externalSvc;
#InjectMocks
PersonService testObj;
}
If I am not mistaken...the thumb rule is you cannot use both together..you either run unit test cases with using MockitojunitRunner or SpringJUnitRunner you cannot use both of them together.

Spring Test mock one dependency

Suppose I have these two parts of XML Spring config in two distinct files;
//daoContext.xml
<bean id="myDao" class="com.MyDao"/>
//logicContext.xml
<bean id="myLogic" class="com.MyLogic">
<constructor-arg ref="myDao"/><!--or other type of injection--?
</bean>
And there is the test class:
#ContextConfiguration("logicContext.xml")
public class BaseLogicTest extends AbstractTestNGSpringContextTests {
#Autowired
private MyLogic myLogic;
#Test
public void testMyTestable() {
//...
}
}
Now, what I want is to be able to mock MyDao class and inject is somehow into MyLogic which is to be injected in BaseLogicTest so I can use MyLogic with a mocked MyDao. Is this possible using Spring/Spring Test?
The simplest solution is load all your xml. And manually replace dependence in test case.
#ContextConfiguration("logicContext.xml")
public class BaseLogicTest extends AbstractTestNGSpringContextTests {
#Autowired
private MyLogic myLogic;
#Before
public void injectTestDoubles() {
myLogic.setMyDao(...);
}
#DirtiesContext
#Test ...//test methods
}
But this corrupts the application context, so you need #DirtiesContext if you need "real" myDao in other test case sharing the same application context.
The most popular solution (my personal opinion :P ) is using mockito and a test-specific xml.
//daoContext.xml
<bean id="myDao" class="com.MyDao"/>
//logicContext.xml
<bean id="myLogic" class="com.MyLogic">
<constructor-arg ref="myDao"/><!--or other type of injection--?
</bean>
//test-logicContext.xml
<bean id="myDao" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.MyDao" />
</bean>
#ContextConfiguration({"logicContext.xml", "test-logicContext.xml"})
public class BaseLogicTest extends AbstractTestNGSpringContextTests {
#Autowired
private MyLogic myLogic;
#Autowired
private MyDao myDao;//retrieve mock so you could define expectations
#Test //test methods
#After public void clearMocks() {
Mockito.reset(myDao);//this is important if you have several test methods
}
}
And this solution works for other mock framework like easyMock.
You can do this by creating a FactoryBean to create the MyDao (or any) mock to be injected into your MyLogic instance.
public class FactoryBeanForMocks<T> implements FactoryBean<T> {
private Class<T> mockClass;
public FactoryBeanForMocks(Class<T> mockClass) {
super();
this.mockClass = mockClass;
}
#Override
public T getObject() throws Exception {
return Mockito.mock(mockClass);
}
#Override
public Class<?> getObjectType() {
return mockClass;
}
#Override
public boolean isSingleton() {
return true;
}
}
Make the factory bean entry into your logicContext.xml so that a mock of MyDao can be injected into MyLogic:
//logicContext.xml
<bean id="myLogic" class="com.MyLogic">
<constructor-arg ref="myDao"/><!--or other type of injection--?
</bean>
<bean id="myDao" class="x.y.z.FactoryBeanForMocks">
<constructor-arg name="mockClass" value="x.y.MyDao"></constructor-arg>
</bean>
And this is your test class:
#ContextConfiguration("logicContext.xml")
public class BaseLogicTest extends AbstractTestNGSpringContextTests {
#Autowired
private MyLogic myLogic;
//You can inject the mock myDao into it so that you can stub/verify method calls on it
#Autowired
private MyDao myDao;
#Test
public void testMyTestable() {
//...
when(myDao.process()).thenReturn("a");//stubbing myDao.process()
assertEquals("a", myLogic.processRequest());// assuming myLogic.processRequest() calls myDao.process()
}
}

Categories

Resources