How to skip @PostConstruct when unit testing
Asked Answered
A

3

8

I have a scheduled task that aggregates data every night. The task runs whenever I start up the application and I would like to stop it from running when I run jUnit tests on the applicaton.

@Scheduled(cron = "0 0 0 1 * ?")
public void SalesDataAggregation() {
    //aggregation
}

Edit

The method above is also being called here

@PostConstruct
public void init(){
    SalesDataAggregation();
}
Accessible answered 31/7, 2018 at 16:32 Comment(4)
Your @scheduledcode shouldn't be running on startup based on the code here. Is this method being called somewhere else?Insert
It was being called elsewhere I added the code aboveAccessible
Its your PostConstruct that is triggering the method. Realistically it doesn't seem that it is appropriate for a post construct call. Maybe some sort of event listener to trigger on start up that could be put in a profile?Puton
I think you'd be better off changing the question to 'How to skip @PostConstruct when unit testing', this is not a scheduling issue...Luthern
I
12

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.

@Primary
public class ClassContainingPostConstruct{   

}
Insert answered 31/7, 2018 at 16:44 Comment(1)
This won't work. Spring will report: "bean class conflicts with existing, non-compatible bean definition of same name"Lightheaded
P
4

You could re-write the PostConstruct containing bean to an EventListener (https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2) to trigger on start-up, which I assume is what the purpose of this could be. That bean could then be tied to certain Profile to only trigger on a specificly enabled profile.

Another option would be to use a property to conditionally trigger it.

public class PostConstructBean {

public boolean isPostConstructEnabled;

public PostConstructBean(@Value("${postconstructenabled}" String value){
   isPostConstructEnabled = Boolean.parseBoolean(value);
}

@PostConstruct
public void init(){
   if(isPostConstructEnabled){
      SalesDataAggregation();
   }else{
      //NOOP
   }
}
}

then just add the property to your environmental properties/overall property file. This has added benefit of allowing you to easier loading/disabling of the bean

Puton answered 31/7, 2018 at 16:54 Comment(0)
S
1

In my case, nothing in my PostConstruct crashes my other tests, only my Mockito.verify, so I decided to keep the injected mock class that the PostConstruct uses and then in my test, re-mock it and re-inject it using Mockito and ReflectionTestUtils. This avoided issues with the bean creation and allowed me to verify only the freshly mocked class:

Class Under Test:

@Component
public class ClassUnderTest
{
    @Autowired
    private MockedClass nameOfActualBeanInClassUnderTest;

    @PostConstruct
    private void postConstructMethod()
    {
        Object param1 = new Object();
        Object param2 = new Object();
        this.nameOfActualBeanInClassUnderTest.mockedClassFunctionBeingHit(param1, param2);
    }
}

Tests Class:

import static org.mockito.Mockito.*;
import org.springframework.test.util.ReflectionTestUtils;

public class Tests
{
    // Class Under Test
    @Autowired
    private ClassUnderTest classUnderTest;

    // Mocked Class
    @MockBean
    private MockedClass mockedClass;

    @Test
    public void actualTestThatAvoidsPostConstruct()
    {
        // ============== Ignore PostConstruct Errors ==============

        // Note: You will probably want to capture the current mocked class
        // to put it back in the class under test so that other tests won't fail
        MockedClass savedMockedClass = 
        (MockedClass)ReflectionTestUtils.getField(this.classUnderTest,
            "nameOfActualBeanInClassUnderTest");

        this.mockedClass = mock(MockedClass.class);
        ReflectionTestUtils.setField(this.classUnderTest,
            "nameOfActualBeanInClassUnderTest",
            this.mockedClass);

        // ============== Setup Test ==============
        Object response = new Object();

        // Set return value when mockedClass' function is hit
        // Note: Only need to pass params if your function actually has them
        when(this.mockedClass.mockedClassFunctionBeingHit(param1, param2))
            .thenReturn(response);

        // ============== Test ==============

        this.classUnderTest.testFunction();

        // ============== Verify ==============

        // Note: Only need to pass params if your function actually has them
        verify(this.mockedClass).mockedClassFunctionBeingHit(param1, param2);

        // ============== Reset Mocked Class ==============
        ReflectionTestUtils.setField(this.classUnderTest,
            "nameOfActualBeanInClassUnderTest",
            savedMockedClass);
    }
}
Schilling answered 29/1, 2019 at 17:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.