6

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();
}
Darren Forsythe
  • 10,712
  • 4
  • 43
  • 54
gnommando
  • 63
  • 1
  • 1
  • 4
  • Your `@scheduled`code shouldn't be running on startup based on the code here. Is this method being called somewhere else? – rlwheeler Jul 31 '18 at 16:34
  • 1
    It was being called elsewhere I added the code above – gnommando Jul 31 '18 at 16:38
  • 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? – Darren Forsythe Jul 31 '18 at 16:39
  • 1
    I think you'd be better off changing the question to 'How to skip @PostConstruct when unit testing', this is not a scheduling issue... – StuPointerException Jul 31 '18 at 16:40

3 Answers3

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{   

}
rlwheeler
  • 518
  • 4
  • 12
  • This won't work. Spring will report: "bean class conflicts with existing, non-compatible bean definition of same name" – jean Nov 25 '19 at 03:36
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

Darren Forsythe
  • 10,712
  • 4
  • 43
  • 54
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);
    }
}
Nick08
  • 157
  • 1
  • 2
  • 10