How to write an intergration test for an @Scheduled cronjob in spring boot applicaiton?

Viewed 73

I have a cronjob application class

ExpireOIrderJob.java

@Service
@AllArgsConstructor
public class ExpireOrdersJob {
  private final ExpireOrderProperties expireOrderProperties;
  private final ExpireOrderUseCase expireOrderUseCase;

  @Scheduled(cron = "${worker.cron.expire-order.cron-time-pattern}")
  public void process() {
    expireOrderUseCase.expire(expireOrderProperties.getExpireTimeInMinutes());
  }
}

I create an integration test for above class such as

ExpireOrderJobIT.java

@RunWith(SpringRunner.class)
@SpringBootTest
@SpringJUnitConfig(SchedulingConfig.class)
@ContextConfiguration(classes = {ExpireOrderConfig.class})
@TestPropertySource(properties = "worker.cron.expire-order.cron-time-pattern=* * * * * *")
public class ExpireOrderJobIT {
  @Autowired
  private ExpireOrdersJob expireOrdersJob;

  @SneakyThrows
  @Test
  public void scheduleRunExpireJob() {
    Thread.sleep(1000L);
    expireOrdersJob.process();
  }
}

I want to my test can coverage all class and function relation the same when I run application on production, so I will not use Mockito @SpyBean such as https://www.baeldung.com/spring-testing-scheduled-annotation. It can not coverage in my service, usecase ... please help

Error in my test throwing

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'net.vinid.ecom.order.worker.cron.ExpireOrdersJob' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1790)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1346)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657)
    ... 30 more
1 Answers

There's quite a mess with your unit test, these three annotations you have:

@RunWith(SpringRunner.class)
@SpringBootTest
@SpringJUnitConfig(SchedulingConfig.class)

Are not compatible between them, as they do pretty much the same, only one is enough. This other annotation @ContextConfiguration(classes = {ExpireOrderConfig.class}) specifies the spring context to be used, currently you only have ExpireOrderConfig class, and based on your error message looks like ExpireOrdersJob is not initialized there. I don't know your full application context therefore I'll suggest a tentative solution, although I may be missing classes from the context.

@SpringBootTest
@ContextConfiguration(classes = {ExpireOrdersJob.class, ExpireOrderUseCase.class, ExpireOrderProperties.class, SchedulingConfig.class, ExpireOrderConfig.class})
@TestPropertySource(properties = "cron.rate=1000") //How often you expect your job to run, you can place your cron here.
class ExpireOrdersJobIntegrationTest {

    @Autowired
    ExpireOrdersJob expireOrdersJob;
    @Autowired
    ExpireOrderUseCase expireOrderUseCase;

    @Test
    void givenExpirationInMinutes_thenExpireOrderUseCaseIsCalledOnceIn1Second() {
        //Based on the rate I set, I expect it to be called in 1s at most.
        await()
            .atMost(Duration.ONE_SECOND)
            .untilAsserted(() -> assertThat(expireOrderUseCase.getExpireCount().get()).isEqualTo(1L));
    }
}

I added in @ContextConfiguration the classes I think you need for this test, however it may be that some are missing or some are unnecessary (e.g if another beans are initialized inside ExpireOrderConfig).

Also, as you can see my assertion is checking for getExpireCount(), I added this counter in method expire simply to see how many times my method is called, however given that you're writing an integration test you should replace this assertion for the side effect you're expecting to verify (e.g a database change, a state change, a rest call, depends on your logic.)

Related