40

我有一个 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(..)@Asyncand注释的那样
  • 由于SpringJUnit4ClassRunner遵守@Async语义

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

在验证结果之前如何等待someService.asyncMethod(testData)' 完成?请注意,如何编写单元测试以使用 Spring 4 和注释验证异步行为的解决方案?不要在这里申请,因为someService.asyncMethod(testData)return void,而不是 a Future<?>

4

6 回答 6

30

为了@Async遵守语义,一些活动@Configuration类将具有@EnableAsync注释,例如

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

  //

}

为了解决我的问题,我引入了一个新的 Spring profile 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.
}
于 2017-02-24T12:38:20.710 回答
18

如果您正在使用 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));
于 2017-09-14T20:48:42.117 回答
15

我已经通过注入 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();
    }
于 2019-07-31T12:49:47.133 回答
4

如果您的方法返回CompletableFuture使用join方法 -文档 CompletableFuture::join

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

于 2018-01-03T15:21:00.987 回答
2

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

@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注释添加到service,只会添加到DAO/repository。除非您需要将其添加到必须是atomic的特定服务方法中。

于 2021-02-24T20:46:42.633 回答
0

除了上述解决方案:

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

于 2021-08-02T13:22:43.833 回答