Provides patterns for unit testing Spring `@Scheduled` and `@Async` methods using JUnit 5, CompletableFuture, Awaitility, and Mockito. Covers mocking task execution and timing, verifying execution counts, testing cron expressions, validating retry behavior, and simulating thread pool behavior. Use when testing background tasks, cron jobs, periodic execution, scheduled tasks, or thread pool behavior.
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill unit-test-scheduled-async
@Scheduled and @Async MethodsPatterns for unit testing Spring @Scheduled and @Async methods with JUnit 5. Test CompletableFuture results, use Awaitility for race conditions, mock scheduled task execution, and validate error handling — without waiting for real scheduling intervals.
@Scheduled method logic@Async method behaviorCompletableFuture results@Async methods directly — bypass Spring's async proxy; the annotation is irrelevant in unit tests@Mock and @InjectMocks (Mockito)CompletableFuture.get(timeout, unit) or await().atMost(...).untilAsserted(...)@Scheduled methods directly — do not wait for cron/fixedRate; the annotation is ignored in unit testsExecutionException wrapping on CompletableFuture.get()Validation checkpoints:
CompletableFuture.get(), assert the returned value before verifying mock interactionsExecutionException is thrown, check .getCause() to identify the root exceptionatMost() duration or reduce pollInterval() until the condition is reachableverify() callsKey patterns — complete examples in references/examples.md:
// @Async: call directly, wait with CompletableFuture.get(timeout, unit)
@Service
class EmailService {
@Async
public CompletableFuture<Boolean> sendEmailAsync(String to) {
return CompletableFuture.supplyAsync(() -> true);
}
}
@Test
void shouldReturnCompletedFuture() throws Exception {
EmailService service = new EmailService();
Boolean result = service.sendEmailAsync("[email protected]").get(5, TimeUnit.SECONDS);
assertThat(result).isTrue();
}
// @Scheduled: call directly, mock the repository
@Component
class DataRefreshTask {
@InjectMocks private DataRepository dataRepository;
@Scheduled(fixedDelay = 60000) public void refreshCache() { /* ... */ }
}
@Test
void shouldRefreshCache() {
when(dataRepository.findAll()).thenReturn(List.of(new Data(1L, "item1")));
dataRefreshTask.refreshCache();
verify(dataRepository).findAll();
}
// Awaitility: use for race conditions with shared mutable state
@Test
void shouldProcessAllItems() {
BackgroundWorker worker = new BackgroundWorker();
worker.processItems(List.of("item1", "item2", "item3"));
Awaitility.await()
.atMost(Duration.ofSeconds(5))
.pollInterval(Duration.ofMillis(100))
.untilAsserted(() -> assertThat(worker.getProcessedCount()).isEqualTo(3));
}
// Mocked dependencies with exception handling
@Test
void shouldHandleAsyncExceptionGracefully() {
doThrow(new RuntimeException("Email failed")).when(emailService).send(any());
CompletableFuture<String> result = service.notifyUserAsync("user123");
assertThatThrownBy(result::get)
.isInstanceOf(ExecutionException.class)
.hasCauseInstanceOf(RuntimeException.class);
}
Full Maven/Gradle dependencies, additional test classes, and execution count patterns: see references/examples.md.
CompletableFuture.get() to prevent hanging tests@Scheduled logic directly — the annotation is ignored in unit testsCompletableFuture.get()@Async self-invocation: calling @Async from another method in the same class executes synchronously — the Spring proxy is bypassedThreadPoolTaskScheduler does not guarantee execution orderatMost(); infinite waits hang the test suite@Scheduled is ignored in unit tests — call methods directly@Async Documentation@Scheduled Documentationreferences/examples.mdToolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Use when implementing any feature or bugfix, before writing implementation code
Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
Expert guidance for systematic backtesting of trading strategies. Use when developing, testing, stress-testing, or validating quantitative trading strategies. Covers "beating ideas to death" methodology, parameter robustness testing, slippage modeling, bias prevention, and interpreting backtest results. Applicable when user asks about backtesting, strategy validation, robustness testing, avoiding overfitting, or systematic trading development.
Cloud laboratory platform for automated protein testing and validation. Use when designing proteins and needing experimental validation including binding assays, expression testing, thermostability measurements, enzyme activity assays, or protein sequence optimization. Also use for submitting experiments via API, tracking experiment status, downloading results, optimizing protein sequences for better expression using computational tools (NetSolP, SoluProt, SolubleMPNN, ESM), or managing protein design workflows with wet-lab validation.
This skill should be used for time series machine learning tasks including classification, regression, clustering, forecasting, anomaly detection, segmentation, and similarity search. Use when working with temporal data, sequential patterns, or time-indexed observations requiring specialized algorithms beyond standard ML approaches. Particularly suited for univariate and multivariate time series analysis with scikit-learn compatible APIs.
Take giuseppe-trisciuoglio/unit-test-scheduled-async from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.