Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing: Call @PostConstruct after defining mocked behaviour

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?

like image 926
dwjohnston Avatar asked Jul 04 '16 01:07

dwjohnston


People also ask

How can Postconstructs be prevented?

The method SalesDataAggregate is running on startup because of the @PostConstruct annotation. If you want to keep it from running during tests you can create the class containing the post construct in your test folder and add the @primary annotation so it takes precedence over the class in your main project.

When PostConstruct is called?

The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization. This method MUST be invoked before the class is put into service. This annotation MUST be supported on all classes that support dependency injection.

Which types of tests use Autowired?

if you are writing unit tests a recommend you use @Mock and @InjectMocks . But if you really want test all the flow and need to inject classes, you can @RunWith(SpringJUnit4ClassRunner. class) and @Autowired your classes.


2 Answers

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

}
like image 177
SS02 Avatar answered Sep 23 '22 22:09

SS02


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

like image 27
UserF40 Avatar answered Sep 23 '22 22:09

UserF40