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.
You're creating a spy that in no way interacts with the actual code:
This would have to be injected into the Spring context as a bean and used everywhere, where
greetingis used. Spring actually provides such functionality: @SpyBean.Instead of autowiring a
greetingand wrapping it with a spy that does not interact with anything in the context, replace the@Autowiredwith@SpyBeanannotation. Thanks to that a spy bean will be created and injected within the Spring context: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:
Thanks to that when you inject the bean, it will be possible to act on it with Mockito (remember to include the
Configclass in the@SpringJUnitConfigannotation).I created another commit in the GitHub repository - the test passes. You can see the whole code there.