如何使用 Spring 4 和注释编写单元测试来验证异步行为?
由于我习惯了 Spring 的(旧的)xml 风格),我花了一些时间才弄清楚这一点。所以我想我回答自己的问题是为了帮助别人。
首先是公开异步下载方法的服务:
@Service
public class DownloadService {
// note: placing this async method in its own dedicated bean was necessary
// to circumvent inner bean calls
@Async
public Future<String> startDownloading(final URL url) throws IOException {
return new AsyncResult<String>(getContentAsString(url));
}
private String getContentAsString(URL url) throws IOException {
try {
Thread.sleep(1000); // To demonstrate the effect of async
InputStream input = url.openStream();
return IOUtils.toString(input, StandardCharsets.UTF_8);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
接下来的测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class DownloadServiceTest {
@Configuration
@EnableAsync
static class Config {
@Bean
public DownloadService downloadService() {
return new DownloadService();
}
}
@Autowired
private DownloadService service;
@Test
public void testIndex() throws Exception {
final URL url = new URL("http://spring.io/blog/2013/01/16/next-stop-spring-framework-4-0");
Future<String> content = service.startDownloading(url);
assertThat(false, equalTo(content.isDone()));
final String str = content.get();
assertThat(true, equalTo(content.isDone()));
assertThat(str, JUnitMatchers.containsString("<html"));
}
}
如果您在 Java 8 中使用相同的示例,您也可以使用 CompletableFuture 类,如下所示:
@Service
public class DownloadService {
@Async
public CompletableFuture<String> startDownloading(final URL url) throws IOException {
CompletableFuture<Boolean> future = new CompletableFuture<>();
Executors.newCachedThreadPool().submit(() -> {
getContentAsString(url);
future.complete(true);
return null;
});
return future;
}
private String getContentAsString(URL url) throws IOException {
try {
Thread.sleep(1000); // To demonstrate the effect of async
InputStream input = url.openStream();
return IOUtils.toString(input, StandardCharsets.UTF_8);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
现在测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class DownloadServiceTest {
@Configuration
@EnableAsync
static class Config {
@Bean
public DownloadService downloadService() {
return new DownloadService();
}
}
@Autowired
private DownloadService service;
@Test
public void testIndex() throws Exception {
final URL url = new URL("http://spring.io/blog/2013/01/16/next-stop-spring-framework-4-0");
CompletableFuture<Boolean> content = service.startDownloading(url);
content.thenRun(() -> {
assertThat(true, equalTo(content.isDone()));
assertThat(str, JUnitMatchers.containsString("<html"));
});
// wait for completion
content.get(10, TimeUnit.SECONDS);
}
}
请注意,如果未指定超时,并且出现任何问题,测试将“永远”继续进行,直到 CI 或您将其关闭。