JUnit 测试 Spring @Async void 服务方法

JUnit-testing a Spring @Async void service method

我有一个 Spring 服务:

@Service
@Transactional
public class SomeService {

    @Async
    public void asyncMethod(Foo foo) {
        // processing takes significant time
    }
}

我对此进行了集成测试 SomeService:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest
@Transactional
public class SomeServiceIntTest {

    @Inject
    private SomeService someService;

        @Test
        public void testAsyncMethod() {

            Foo testData = prepareTestData();

            someService.asyncMethod(testData);

            verifyResults();
        }

        // verifyResult() with assertions, etc.
}

这是问题所在:

testAsyncMethod 线程会将调用 someService.asyncMethod(testData) 分叉到它自己的工作线程中,然后直接继续执行 verifyResults(),可能在前一个工作线程完成工作之前。

如何等待 someService.asyncMethod(testData) 完成后再验证结果?请注意 How do I write a unit test to verify async behavior using Spring 4 and annotations? 的解决方案不适用于此处,因为 someService.asyncMethod(testData) returns void,而不是 Future<?> .

要遵守@Async语义,,例如

@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {

  //

}

为了解决我的问题,我引入了一个新的 Spring 配置文件 non-async

如果 non-async 配置文件 活动,则使用 AsyncConfiguration

@Configuration
@EnableAsync
@EnableScheduling
@Profile("!non-async")
public class AsyncConfiguration implements AsyncConfigurer {

  // this configuration will be active as long as profile "non-async" is not (!) active

}

如果非异步配置文件 处于 活动状态,则使用 NonAsyncConfiguration

@Configuration
// notice the missing @EnableAsync annotation
@EnableScheduling
@Profile("non-async")
public class NonAsyncConfiguration {

  // this configuration will be active as long as profile "non-async" is active

}

现在在有问题的 JUnit 测试 class 中,我明确激活 "non-async" 配置文件以相互排除异步行为:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest
@Transactional
@ActiveProfiles(profiles = "non-async")
public class SomeServiceIntTest {

    @Inject
    private SomeService someService;

        @Test
        public void testAsyncMethod() {

            Foo testData = prepareTestData();

            someService.asyncMethod(testData);

            verifyResults();
        }

        // verifyResult() with assertions, etc.
}

如果您正在使用 Mockito(直接或通过 Spring 测试支持 @MockBean),它有一个验证模式,超时正好适合这种情况: https://static.javadoc.io/org.mockito/mockito-core/2.10.0/org/mockito/Mockito.html#22

someAsyncCall();
verify(mock, timeout(100)).someMethod();

强大的库 Awaitility 功能更强大,它有很多选项可以处理异步断言。示例:

someAsyncCall();
await().atMost(5, SECONDS)
  .untilAsserted(() -> assertThat(userRepo.size()).isEqualTo(1));

如果您的方法 returns CompletableFuture 使用 join 方法 - documentation CompletableFuture::join

此方法等待异步方法完成并 returns 结果。任何遇到的异常都会在主线程中重新抛出。

我通过注射完成了 ThreadPoolTask​​Executor

然后

executor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);

在验证结果之前, 如下所示:

  @Autowired
  private ThreadPoolTaskExecutor executor;

    @Test
    public void testAsyncMethod() {

        Foo testData = prepareTestData();

        someService.asyncMethod(testData);

        executor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);

        verifyResults();
    }

只是为了扩展@bastiat 的答案,在我看来这应该被认为是正确的,如果您正在与多个执行者一起工作,您还应该指定 TaskExecutor。所以你需要注入你希望等待的正确的。所以,假设我们有以下 配置 class.

@Configuration
@EnableAsync
public class AsyncConfiguration {

    @Bean("myTaskExecutor")
    public TaskExecutor myTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(15);
        executor.setCoreCapacity(10);
        executor.setQueueCapacity(Integer.MAX_VALUE);
        executor.setThreadNamePrefix("MyTaskExecutor-");
        executor.initialize();
        return executor;
    }

    // Everything else

}

然后,您将获得如下所示的服务。

@Service
public class SomeServiceImplementation {

    @Async("myTaskExecutor")
    public void asyncMethod() {
         // Do something
    }

    // Everything else

}

现在,扩展@bastiat 的回答,测试将如下所示。

@Autowired
private SomeService someService;

@Autowired
private ThreadPoolTaskExecutor myTaskExecutor;

@Test
public void testAsyncMethod() {

    Foo testData = prepareTestData();

    this.someService.asyncMethod(testData);

    this.myTaskExecutor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);

    this.verifyResults();

    // Everything else
}

另外,我有一个与问题无关的小建议。我不会将 @Transactional 注释添加到 服务 ,只会添加到 DAO/repository。除非你需要将它添加到必须是 atomic.

的特定服务方法

以上解决方案的补充:

 @Autowired
  private ThreadPoolTaskExecutor pool;

    @Test
    public void testAsyncMethod() {
        // call async method
        someService.asyncMethod(testData);

        boolean awaitTermination = pool.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
        assertThat(awaitTermination).isFalse();

        // verify results
    }