【问题标题】:Using Mockito to stub and execute methods for testing使用 Mockito 存根并执行测试方法
【发布时间】:2013-04-12 16:04:15
【问题描述】:

我最近问了几个面向 jUnit 和 Mockito 的问题,但我仍然很难掌握它的窍门。这些教程都是针对非常简单的示例,所以我正在努力扩大我的测试用例以适用于我的课程。

我目前正在尝试为我在 web 应用程序中的一个代理中使用的方法编写一些测试用例。该方法与代理内部的其他几个方法交互以验证某些对象。我现在只想测试这个方法。

这是我尝试做的:

  1. 像这样创建我的代理的 Mockito 对象:

    MyProcessingAgent mockMyAgent = Mockito.mock(MyProcessingAgent.class);

  2. 使用 Mockito.when 设置存根(希望是正确的术语),如下所示:

    Mockito.when(mockMyAgent.otherMethod(Mockito.any(arg1)).thenReturn(requiredReturnArg);

  3. 尝试像这样执行我的方法:

    List myReturnValue = mockMyAgent.methodThatNeedsTestCase();

我期待 myReturnValue 中的内容,但收到了 0,所以我尝试调试。当我调用该方法时,它永远不会执行。我在方法的第一行有一个永远不会被触及的调试点。

如果我想在类的一个方法中执行代码,但强制类中的其他方法(尝试与外部世界中的数据库交互的方法)返回伪造的值。 Mockito 可以做到这一点吗?

看来我目前的方法不是正确的测试方式,但我不确定如何前进。我可以模拟我的类并让一种方法像正常一样执行,而其他方法被存根以返回我的给定值,这样我就不必在测试这一种方法期间处理数据访问?

【问题讨论】:

    标签: java junit mocking mockito


    【解决方案1】:

    您将MockSpy 混淆了。

    在模拟中,所有方法都被存根并返回“智能返回类型”。这意味着调用模拟类的任何方法什么都不做,除非你指定行为。

    在 spy 中,类的原始功能仍然存在,但您可以在 spy 中验证方法调用并覆盖方法行为。

    你想要的是

    MyProcessingAgent mockMyAgent = Mockito.spy(MyProcessingAgent.class);
    

    一个简单的例子:

    static class TestClass {
    
        public String getThing() {
            return "Thing";
        }
    
        public String getOtherThing() {
            return getThing();
        }
    }
    
    public static void main(String[] args) {
        final TestClass testClass = Mockito.spy(new TestClass());
        Mockito.when(testClass.getThing()).thenReturn("Some Other thing");
        System.out.println(testClass.getOtherThing());
    }
    

    输出是:

    Some Other thing
    

    注意:您真的应该尝试模拟正在测试的类的依赖项而不是类本身。

    【讨论】:

    • 请参阅我关于间谍的声明,如果您不同意,请告诉我。
    • @JohnB 我认为你是对的。我已经通过答案扩展了。我没有意识到 OP 正在内部调用该方法。
    • 感谢详细的描述,非常有帮助。所以,两个答案都建议(澄清)是我用 Mock 攻击了错误的类。相反,我应该模拟我的类需要的所有数据访问对象,然后使用我的类的普通实例进行测试?
    • 所以,我错了。至少在我的盒子里,间谍会接到内部电话。我设置了一个 m1() 调用 m2() 的场景。我监视对象并存根 m2()。在我的测试中,我调用 m1()。永远不会到达对象中的 m2(),调用存根。恕我直言,这仍然是不好的做法,但很高兴知道。
    • @JohnB 是的,看来我们有点过分了。 spy 工作正常。显然不是这样做的方法真的,但一些糟糕的单元测试可能总比没有好......
    【解决方案2】:

    因此,嘲笑被测类的想法是对测试实践的厌恶。你不应该这样做。因为你已经这样做了,所以你的测试是进入 Mockito 的模拟类而不是你的被测类。

    Spying 也不会起作用,因为这只提供了 spyed 类的包装器/代理。一旦在类内部执行,它将不会通过代理,因此不会命中间谍。更新:虽然我相信 Spring 代理确实如此,但 Mockito 间谍似乎并非如此。我设置了一个场景,其中方法m1() 调用m2()。我监视对象并将m2() 存根到doNothing。当我在测试中调用m1() 时,没有达到类的m2()。 Mockito 调用存根。因此,使用间谍来完成所要求的事情是可能的。但是,我要重申,我认为这是不好的做法(恕我直言)。

    您应该模拟被测类所依赖的所有类。这将允许您控制被测方法调用的方法的行为,因为您可以控制这些方法调用的类。

    如果您的类创建其他类的实例,请考虑使用工厂。

    【讨论】:

    • +1 用于使用像 anathema 这样的词,看起来很苛刻,但我想我明白你的意思。我仍在努力完成测试,我想我只是从完全错误的角度来的。
    • A 字可能是真的。您甚至在进行单元测试这一事实是一个好兆头。保持。干杯!
    【解决方案3】:

    你几乎得到它。问题是 Class Under Test (CUT) 不是为单元测试而构建的 - 它真的不是 TDD'd。

    这样想……

    • 我需要测试一个类的函数 - 我们称之为 myFunction
    • 该函数调用另一个类/服务/数据库上的函数
    • 该函数还调用 CUT 上的另一个方法

    在单元测试中

    • 应该在上面创建一个具体的CUT@Spy
    • 您可以@Mock所有其他类/服务/数据库(即外部依赖项)
    • 可以CUT 中调用的其他函数存根,但这并不是真正应该如何进行单元测试

    为了避免执行您未严格测试的代码,您可以将该代码抽象为可以@Mocked 的东西。

    在这个非常简单的例子中,创建对象的函数将很难测试

    public void doSomethingCool(String foo) {
        MyObject obj = new MyObject(foo);
    
        // can't do much with obj in a unit test unless it is returned
    }
    

    但是使用服务获取 MyObject 的函数很容易测试,因为我们已将难以/不可能测试的代码抽象为使该方法可测试的东西。

    public void doSomethingCool(String foo) {
        MyObject obj = MyObjectService.getMeAnObject(foo);
    }
    

    因为可以模拟 MyObjectService 并验证 .getMeAnObject() 是使用 foo 变量调用的。

    【讨论】:

    • 如果 myFunctionmyFunction2CUT 的一部分,并且 myFunction 调用 myFunction2 如您的示例所述,基于 Spy 的部分模拟不是唯一的选择吗? myFunction2 是否应该从 CUT 中移出并“注入”,即使它是 CUT 责任的一个组成部分?我错过了什么?
    • 我认为您没有遗漏任何东西。基于 Spy 的部分模拟是最好的选择,尽管您可以同时测试这两种方法。我的示例更多地是关于当 CUT 中的方法创建您可能想要在其中验证行为或断言值的对象时的问题,这可以通过重构 CUT 来完成使用工厂或外部服务来创建那些可以被模拟和注入的对象。
    【解决方案4】:

    简短回答

    你的情况怎么办:

    int argument = 5; // example with int but could be another type
    Mockito.when(mockMyAgent.otherMethod(Mockito.anyInt()).thenReturn(requiredReturnArg(argument));
    

    长答案

    实际上你想做的事情是可能的,至少在 Java 8 中。也许你没有从其他人那里得到这个答案,因为我使用的是允许这样做的 Java 8,而这个问题是在 Java 8 发布之前(允许传递函数,而不仅仅是将值传递给其他函数)。

    让我们模拟对数据库查询的调用。此查询返回 HotelTable 中 FreeRoms = X 且 StarNumber = Y 的所有行。 我在测试期间期望的是,这个查询将返回一个不同酒店的列表:每个返回的酒店都有相同的值 X 和 Y,而其他值,我将根据我的需要决定它们。下面的例子很简单,当然你可以把它变得更复杂。

    所以我创建了一个函数,它会返回不同的结果,但它们都有 FreeRoms = X 和 StarNumber = Y。

    static List<Hotel> simulateQueryOnHotels(int availableRoomNumber, int starNumber) {
        ArrayList<Hotel> HotelArrayList = new ArrayList<>();
        HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Rome, 1, 1));
        HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Krakow, 7, 15));
        HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Madrid, 1, 1));
        HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Athens, 4, 1));
    
        return HotelArrayList;
    }
    

    也许 Spy 更好(请尝试),但我是在模拟课程中做到的。我是怎么做的(注意 anyInt() 值):

    //somewhere at the beginning of your file with tests...
    @Mock
    private DatabaseManager mockedDatabaseManager;
    
    //in the same file, somewhere in a test...
    int availableRoomNumber = 3;
    int starNumber = 4;
    // in this way, the mocked queryOnHotels will return a different result according to the passed parameters
    when(mockedDatabaseManager.queryOnHotels(anyInt(), anyInt())).thenReturn(simulateQueryOnHotels(availableRoomNumber, starNumber));
    

    【讨论】:

      猜你喜欢
      • 2018-05-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多