JUnit测试Spring @Async void服务方法

时间:2017-02-24 12:38:21

标签: java spring junit spring-async

我有一个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.
}

问题在于:

  • SomeService.asyncMethod(..)注释为@Async
  • ,因为SpringJUnit4ClassRunner遵守@Async语义

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)会返回void,而不是Future<?>

6 个答案:

答案 0 :(得分:15)

要遵守@Async语义,some active @Configuration class will have the @EnableAsync annotation,例如

@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测试类中,我明确激活了“非异步”配置文件,以便相互排除异步行为:

@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.
}

答案 1 :(得分:7)

如果您正在使用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(在互联网上找到它,没有尝试过)。 https://blog.jayway.com/2014/04/23/java-8-and-assertj-support-in-awaitility-1-6-0/

someAsyncCall();
await().until( () -> assertThat(userRepo.size()).isEqualTo(1) );

答案 2 :(得分:3)

我已经通过注射完成了 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();
    }

答案 3 :(得分:1)

如果您的方法返回CompletableFuture,请使用join方法 - documentation CompletableFuture::join

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

答案 4 :(得分:1)

只是对上述解决方案的补充:

 @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
    }

答案 5 :(得分:0)

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

@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/存储库。除非您需要将其添加到必须是原子性的特定服务方法中。