使用事务模板集成测试手动事务

Integration test a manual transaction with transaction template

当我尝试测试使用手动事务的方法时,我的事务模板出现空指针异常。当我 运行 Spring 中的应用程序启动时,它按预期工作。

@Autowired
TransactionTemplate template;

public CompletableFuture<MyResultEntity> addToA(BInput input) {
    return CompletableFuture
        .supplyAsync(
            () -> template.execute(status -> {
              A a = aRepository.findOne(input.getA());
              List<B> addedBs = saveBs(input.getB(), a);
              return new MyResultEntity(a, addedBs);
            }), MyCustomExecutor());
}

我试过使用模拟模板并像这样注入它:

@Mock
private TransactionTemplate transactionTemplate;

@InjectMocks
private MyClass myClass;

我也尝试过用以下方式注释我的测试:

@RunWith(SpringJUnit4ClassRunner.class)

调试此配置时,模板实际上已注入并且不再为空。但是由于我有兴趣测试交易中的操作,我不想模拟它所以我使用:

when(transactionTemplate.execute(Mockito.any())).thenCallRealMethod();

这将引发一个新的空指针异常,因为事务模板尝试使用 TransactionManager 并且它仍然是空的。

如何在事务模板中对我的方法调用进行单元测试?

如果 TransactionManagernull 这意味着 Spring 可能没有在测试上下文中加载所有必要的依赖项。
无论如何,如果您需要调用 execute() 方法,为什么还要模拟 TransactionTemplate
您的测试看起来像 integration/sociable 测试而不是单元测试。
如果是这样,你不需要模拟任何东西。
如果你想编写一个单元测试来测试 addToA() 方法中的实际逻辑,你应该使用 mock 而不是部分模拟。
供应商中使用的模拟依赖项提供并断言返回了预期的 MyResultEntity 实例。

请注意,您的单元测试将具有有限的价值,并且可能被认为是脆弱的,因为它仅断言调用了一系列方法。通常,您希望根据更具体的逻辑(例如 computation/extraction/transformation)来断言行为。

这是一个例子(没有测试,但它应该在途中给出一个想法):

@Mock
Repository ARepositoryMock;

@Mock
Repository BRepositoryMock;

@Test
public void addToA() throws Exception {

    BInput input = new BInput();

    // record mock behaviors
    A aMockByRepository = Mockito.mock(A.class);
    List<B> listBMockByRepository = new arrayList<>();
    Mockito.when(ARepositoryMock.findOne(input.getA())).thenReturn(aMockByRepository);
    Mockito.when(BRepositoryMock.saveBs(input.getB(), aMockByRepository)).thenReturn(listBMockByRepository);

    // action
    CompletableFuture<MyResultEntity> future = myObjectUnderTest.addToA(input);
    //assertion
    MyResultEntity actualResultEntity = future.get();
    Assert.assertEquals(aMockByRepository, actualResultEntity.getA());
    Assert.assertEquals(listBMockByRepository, actualResultEntity.getListOfB());
}

我通常做的不是调用真实的方法,而是模拟真实的行为。在 mock 中调用真正的方法将失败,因为 mock 不是在 springs 注入上下文中管理的。好吧,确切地说,您可以通过将它们添加到测试配置(普通 SpringMVC)或使用 @MockBean(spring 引导)使它们存在于注入上下文中。但它们仍然只是作为依赖注入。但不会收到任何依赖项。对于单元测试,这通常是所需的行为。

所以只需执行以下操作:

when(_transactionTemplate.execute(any())).thenAnswer(invocation -> invocation.<TransactionCallback<Boolean>>getArgument(0).doInTransaction(_transactionStatus));

_transactionStatus 可以是一个 mock 本身来测试回调中状态的使用。

mocking 是 mock 的用途:)