集成测试手动事务与事务模板

时间:2018-04-19 08:34:41

标签: java unit-testing mockito spring-transactions

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

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

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

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

这会抛出一个新的空指针异常,因为事务模板试图使用TransactionManager并且仍为null。

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

2 个答案:

答案 0 :(得分:2)

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

所以要做类似的事情:

import re
s  ='417,364.4265,2535.2258,16.7616,143.5451,0,0 ; Leviathan'

re.split(r' *[,;] *',s)
# ['417', '364.4265', '2535.2258', '16.7616', '143.5451', '0', '0', 'Leviathan']

_transactionStatus可以是一个模拟自己,以测试回调中状态的使用。

嘲笑是用于模拟的:)

答案 1 :(得分:1)

如果TransactionManagernull,则意味着Spring可能没有在测试环境中加载所有必需的依赖项。
无论如何,如果你需要调用TransactionTemplate方法,为什么要嘲笑execute()? 您的测试看起来像集成/社交测试,而不是单元测试 如果是这样的话,你不需要嘲笑任何东西 如果你想编写一个测试addToA()方法中实际逻辑的单元测试,你应该使用mock而不进行局部模拟。
供应商中使用的模拟依赖关系提供并声明返回预期的MyResultEntity实例。

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

这是一个例子(没有经过测试,但它应该在路上给出一个想法):

@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());
}