使用Mockito Mock嵌套方法调用

2025/03/19

1. 概述

在本教程中,我们将了解如何使用Mockito存根(特别是深层存根)Mock嵌套方法调用。要了解有关使用Mockito进行测试的更多信息,请查看我们全面的Mockito系列

2. 解释问题

在复杂代码(尤其是遗留代码)中,有时很难初始化单元测试所需的所有对象。我们很容易在测试中引入许多不需要的依赖项,另一方面,Mock这些对象可能会导致空指针异常

让我们看一个代码示例,探讨这两种方法的局限性。

对于这两者,我们都需要一些类来测试。首先,让我们添加一个NewsArticle类:

public class NewsArticle {
    String name;
    String link;

    public NewsArticle(String name, String link) {
        this.name = name;
        this.link = link;
    }

    // Usual getters and setters
}

此外,我们需要一个Reporter类:

public class Reporter {
    String name;

    NewsArticle latestArticle;

    public Reporter(String name, NewsArticle latestArticle) {
        this.name = name;
        this.latestArticle = latestArticle;
    }

    // Usual getters and setters
}

最后,让我们创建一个NewsAgency类:

public class NewsAgency {
    List<Reporter> reporters;

    public NewsAgency(List<Reporter> reporters) {
        this.reporters = reporters;
    }

    public List<String> getLatestArticlesNames(){
        List<String> results = new ArrayList<>();
        for(Reporter reporter : this.reporters){
            results.add(reporter.getLatestArticle().getName());
        }
        return results;
    }
}

理解它们之间的关系很重要。首先,NewsArticle由Reporter报道,而Reporter为NewsAgency工作。

NewsAgency包含一个getLatestArticlesNames()方法,该方法返回NewsAgency所有reporters撰写的最新文章的名称。此方法将被编写我们的单元测试。

让我们通过初始化所有对象来首次尝试这个单元测试。

3. 初始化对象

在我们的测试中,作为第一种方法,我们将初始化所有对象

public class NewsAgencyTest {
    @Test
    void getAllArticlesTest(){
        String title1 = "new study reveals the dimension where the single socks disappear";
        NewsArticle article1 = new NewsArticle(title1,"link1");
        Reporter reporter1 = new Reporter("Tom", article1);

        String title2 = "secret meeting of cats union against vacuum cleaners";
        NewsArticle article2 = new NewsArticle(title2,"link2");
        Reporter reporter2 = new Reporter("Maria", article2);

        List<String> expectedResults = List.of(title1, title2);

        NewsAgency newsAgency = new NewsAgency(List.of(reporter1, reporter2));
        List<String> actualResults = newsAgency.getLatestArticlesNames();
        assertEquals(expectedResults, actualResults);
    }
}

我们可以看到,当我们的对象变得越来越复杂时,所有对象的初始化都会变得很繁琐。由于Mock正是为此目的,我们将使用它们来简化和避免繁琐的初始化

4. Mock对象

让我们使用Mock来测试相同的方法getLatestArticlesNames():

@Test
void getAllArticlesTestWithMocks(){
    Reporter mockReporter1 = mock(Reporter.class);
    String title1 = "cow flying in London, royal guard still did not move";
    when(mockReporter1.getLatestArticle().getName()).thenReturn(title1);
    Reporter mockReporter2 = mock(Reporter.class);
    String title2 = "drunk man accidentally runs for mayor and wins";
    when(mockReporter2.getLatestArticle().getName()).thenReturn(title2);
    NewsAgency newsAgency = new NewsAgency(List.of(mockReporter1, mockReporter2));

    List<String> expectedResults = List.of(title1, title2);
    assertEquals(newsAgency.getLatestArticlesNames(), expectedResults);
}

如果我们尝试按原样执行此测试,我们将收到空指针异常。根本原因是对mockReporter1.getLastestArticle()的调用返回null,这是预期的行为:Mock是对象的无效版本

5. 使用深层存根

深层存根是Mock嵌套调用的简单解决方案,深层存根可帮助我们利用Mock并仅存根测试中需要的调用

让我们在示例中使用它,我们将使用Mock和深度存根重写单元测试:

@Test
void getAllArticlesTestWithMocksAndDeepStubs(){
    Reporter mockReporter1 = mock(Reporter.class, Mockito.RETURNS_DEEP_STUBS);
    String title1 = "cow flying in London, royal guard still did not move";
    when(mockReporter1.getLatestArticle().getName()).thenReturn(title1);
    Reporter mockReporter2 = mock(Reporter.class, Mockito.RETURNS_DEEP_STUBS);
    String title2 = "drunk man accidentally runs for mayor and wins";
    when(mockReporter2.getLatestArticle().getName()).thenReturn(title2);
    NewsAgency newsAgency = new NewsAgency(List.of(mockReporter1, mockReporter2));

    List<String> expectedResults = List.of(title1, title2);
    assertEquals(newsAgency.getLatestArticlesNames(), expectedResults);
}

添加Mockito.RETURNS_DEEP_STUBS使我们能够访问所有嵌套方法和对象。在我们的代码示例中,我们不需要Mock mockReporter1中的多层对象来访问mockReporter1.getLatestArticle().getName()。

6. 总结

在本文中,我们学习了如何使用深度存根来解决Mockito的嵌套方法调用问题。

我们应该记住,必须使用它们通常是违反迪米特定律的症状,迪米特定律是面向对象编程中的一条指导原则,它有利于低耦合和避免嵌套方法调用。因此,深层存根应该留给遗留代码,在干净的现代代码中,我们应该倾向于重构嵌套调用。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章