1

Spring 6, Quartz, and a SimpleTrigger based scheduled task.

@Component
@Slf4j
public class Greeting {

    public void sayHello() {
        log.debug("Hello at {}:", LocalDateTime.now());
    }
}

Quartz config:


@Configuration
class QuartzConfig{
    @Bean
    MethodInvokingJobDetailFactoryBean greetingJobDetailFactoryBean() {
        var jobFactory = new MethodInvokingJobDetailFactoryBean();
        jobFactory.setTargetBeanName("greeting");
        jobFactory.setTargetMethod("sayHello");
        return jobFactory;
    }

    @Bean
    public SimpleTriggerFactoryBean simpleTriggerFactoryBean() {
        SimpleTriggerFactoryBean simpleTrigger = new SimpleTriggerFactoryBean();
        simpleTrigger.setJobDetail(greetingJobDetailFactoryBean().getObject());
        simpleTrigger.setStartDelay(1_000);
        simpleTrigger.setRepeatInterval(5_000);
        return simpleTrigger;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        var factory = new SchedulerFactoryBean();
        factory.setTriggers(
                simpleTriggerFactoryBean().getObject(),
                cronTriggerFactoryBean().getObject()
        );
        return factory;
    }

And I tried to use awaitility to check the invocations.


@SpringJUnitConfig(value = {
        QuartzConfig.class,
        Greeting.class
})
public class GreetingTest {

    @Autowired
    Greeting greeting;

    Greeting greetingSpy;

    @BeforeEach
    public void setUp() {
        this.greetingSpy = spy(greeting);
    }

    @Test
    public void whenWaitTenSecond_thenScheduledIsCalledAtLeastTenTimes() {
        await()
                .atMost(Duration.ofSeconds(10))
                .untilAsserted(() -> verify(greetingSpy, atLeast(1)).sayHello());
    }
}

Running the tests, it is failed.

org.awaitility.core.ConditionTimeoutException: Assertion condition defined as a com.example.demo.GreetingTest 
Wanted but not invoked:
greeting.sayHello();
-> at com.example.demo.GreetingTest.lambda$whenWaitTenSecond_thenScheduledIsCalledAtLeastTenTimes$0(GreetingTest.java:36)
Actually, there were zero interactions with this mock.
 within 10 seconds.

In the jobDetailFactorBean, I used jobFactory.setTargetBeanName("greeting"); to setup the target beans here, it should pass the Greeting bean directly.

Updated: resolved myself, check here.

Hantsy
  • 8,006
  • 7
  • 64
  • 109

1 Answers1

0

You're creating a spy that in no way interacts with the actual code:

    @BeforeEach
    public void setUp() {
        this.greetingSpy = spy(greeting);
    }

This would have to be injected into the Spring context as a bean and used everywhere, where greeting is used. Spring actually provides such functionality: @SpyBean.

Instead of autowiring a greeting and wrapping it with a spy that does not interact with anything in the context, replace the @Autowired with @SpyBean annotation. Thanks to that a spy bean will be created and injected within the Spring context:

    @SpyBean
    Greeting greeting;

I created a commit in GitHub repository, where you can see the whole code - the test passes. I had to add the cronTriggerFactoryBean() method to the configuration as it is omitted in your question.


If you cannot use Spring Boot, you can create the spy within Spring context yourself using configuration:

static class Config {

    @Bean
    @Primary
    Greeting greeting() {
        return spy(new Greeting());
    }
}

Thanks to that when you inject the bean, it will be possible to act on it with Mockito (remember to include the Config class in the @SpringJUnitConfig annotation).

I created another commit in the GitHub repository - the test passes. You can see the whole code there.

Jonasz
  • 1,617
  • 1
  • 13
  • 19
  • I do not use Spring Boot here. I know well about the MockBean and SpyBean in Spring Boot. – Hantsy Sep 24 '22 at 13:43
  • I edited the answer and added a solution for this case as well. – Jonasz Sep 25 '22 at 07:04
  • Already resolved myself, check [the tests](https://github.com/hantsy/spring6-sandbox/blob/master/quartz/src/test/java/com/example/demo/GreetingTest.java). BTW, your spy bean does not spy the target bean. – Hantsy Sep 25 '22 at 12:21
  • If the answer is a proper solution to the problem, accept it. No need to spy a specific instance in this case as only the number of invocations is verified. – Jonasz Sep 26 '22 at 05:49