如何使用JUnit或Mockito测试匿名方法?

时间:2012-08-15 00:08:10

标签: java junit mockito

我有简单的类,但有匿名代码块。我需要用测试来覆盖这个课程。

public class CleanerTask {

    private final Logger log = LoggerFactory.getLogger(getClass());
    DataWarehouseMessageDao dwMessageDao;
    int cleanerDelay = 0;
    TransactionTemplate template;

    public CleanerTask(DataWarehouseMessageDao dwMessageDao, int cleanerDelay, TransactionTemplate template) {
        this.dwMessageDao = dwMessageDao;
        this.cleanerDelay = cleanerDelay;
        this.template = template;
    }

    public void clean() {
        log.info("Cleaner started");
        final Date olderThan = new Date();
        olderThan.setDate(olderThan.getDate() + cleanerDelay);
        template.execute(new TransactionCallback<Date>() {
            @Override
            public Date doInTransaction(TransactionStatus transactionStatus) {
                dwMessageDao.deleteAllByStatusAndDate(DataWarehouseMessageStatus.SAVED.getValue(), olderThan);
                return olderThan;
            }
        });
    }
}

并测试:

@RunWith(MockitoJUnitRunner.class)
public class CleanerTaskTest {

    final static int CLEANER_DELAY = 5;

    @Mock
    DataWarehouseMessageDao dao;

    @Mock
    TransactionTemplate template;

    CleanerTask cleanerTask;

    @Before
    public void setUp() throws Exception {
        cleanerTask = new CleanerTask(dao, CLEANER_DELAY, template);
    }

    @Test
    public void successfulScenario() {
        try {
            cleanerTask.clean();
            verify(template, times(1)).execute(isA(TransactionCallback.class));
            //that verify was not triggered    
            //verify(dao, times(1)).deleteAllByStatusAndDate(anyInt(), isA(Date.class));
        } catch (Exception e) {
            e.printStackTrace();
            fail("No exceptions must occur");
        }
    }
}
  

注释行不起作用。日志:通缉但未调用:   dao.deleteAllByStatusAndDate(       ,       isA(java.util.Date));    - &GT;在com.nxsystems.dw.publisher.handler.CleanerTaskTest.successfulScenario(CleanerTaskTest.java:52)   实际上,这个模拟没有互动。

     

在   com.nxsystems.dw.publisher.handler.CleanerTaskTest.successfulScenario(CleanerTaskTest.java:52)     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at   sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)     在   sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)     在   org.junit.runners.model.FrameworkMethod $ 1.runReflectiveCall(FrameworkMethod.java:44)     在   org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)     在   org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)     在   org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)     在   org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)     在   org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)     在   org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)     在org.junit.runners.ParentRunner $ 3.run(ParentRunner.java:193)at   org.junit.runners.ParentRunner $ 1.schedule(ParentRunner.java:52)at at   org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)at at   org.junit.runners.ParentRunner.access $ 000(ParentRunner.java:42)at at   org.junit.runners.ParentRunner $ 2.evaluate(ParentRunner.java:184)at at   org.junit.runners.ParentRunner.run(ParentRunner.java:236)at at   org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)     在   org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)     在org.junit.runner.JUnitCore.run(JUnitCore.java:157)at   com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:62)

此时启动此tets时,调试器不会进入匿名块。 那么如何让Mockito进入匿名区域?

3 个答案:

答案 0 :(得分:12)

为什么它不起作用

这里你的问题是你的测试中的TransactionTemplate是一个模拟。因此它具有与TransactionTemplate相同的界面,但它不知道如何表现。你负责实施 - 这就是模拟的全部要点。您在代码中明确调用template.execute(),这就是您的第一次验证通过的原因。但是execute()不是来自Spring的那个(或者更准确地说template在你的测试中不是Spring的TransactionTemplate的实例,它只是 一个模拟它是 - 好吧,让我们说它是“空的”,因为它是在一个模拟器上调用的,你没有告诉模拟如何调用execute()就可以表现出来。

我将如何解决它

在这种情况下,我会真的阻止您进行此类单元测试,因为您在此处测试实现。至少在我看来,你应该测试的是功能意味着给定某些条件,某事发生时然后应该会出现一些结果。这需要将其更改为集成测试(使用假设DBUnit或其他任何东西)并声明您是否实际删除了应该删除的内容。我的意思是你真正关心的是什么 - 知道某些方法被调用或者你希望实际发生了什么?

你怎么可以,但恕我直言不应该修复它。

但是如果你真的想要测试那段匿名代码,那么我只需将它(整个匿名类)提取到一个单独的类中,并为该新类编写单元测试等等正是因为它的doInTransaction()方法。在这种情况下,您可以使用new创建它,在其中设置模拟DataWarehouseMessageDao,然后只需执行verify()

答案 1 :(得分:2)

你不应该改变你的代码,这是对的。在单元测试中,您应该使用 ArgumentCaptor 来捕获传递的参数,验证该实例是TransactionCallback的类型并在其上调用doInTransaction方法,而不是isA检查。因此,您将能够验证是否已使用预期参数调用dao(建议您可以使用 eq 匹配器来验证确切的值)。

确实,在这个测试中你会一次测试两件事,但这只是因为你的实现,我并不是说这是错的。使用某些业务逻辑创建新实例总是会增加代码中的耦合,但这并不意味着我们不应该使用语言能力来实现这一点。

答案 2 :(得分:0)

这是处理transactionTemplate(Java 8+)的另一种方法:

    TransactionTemplate transactionTemplate = mock(TransactionTemplate.class);
    when(transactionTemplate.execute(any(TransactionCallback.class)))
       .then(invocation -> ((TransactionCallback<YourReturnClass>) invocation.getArgument(0))
       .doInTransaction(any(TransactionStatus.class)));

此解决方案将在此伪造交易中执行代码,您之后可以验证答案。