如何使用Mockito消除外部依赖?

时间:2017-05-08 23:06:51

标签: java unit-testing junit mocking mockito

我在掌握Mockito的概念时遇到了一些麻烦。我已经写了一个小程序来帮助,但我无法做到我想做的事。

这是我的代码:

// WeatherDemo.java:

package com.abc;

public class WeatherDemo {
    public String getForecast() {
        // Get the high remperature for today, and return back to the caller one of these values:
        // cold, mild, or hot
        // cold will be returned if the high temp is forecast to be less than 60.
        // hot will be returned if the high temp is forecast to be more than 79.
        // Otherwise, mild will be returned (this indicates a high temp in the 60s or 70s).
        int highTemp = getHighTemp();
        if (highTemp < 60)
            return("cold");
        if (highTemp > 79)
            return("hot");
        return("mild");
    }

    public int getHighTemp() {
        // Because this is a demo, we don't have access to any source (web service, DB, etc.) to get the high temp.
        // Just hard code a value here, but remember that if this were a real application, we would be dynamically
        //   retrieving the day's high temperature from some external source.
        int highTemp = 32;
        return(highTemp);
    }
}

=============================================== =================================

// TestWeatherDemo.java:

package com.abc;

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.mockito.Mockito;

public class TestWeatherDemo {
    @Test
    public void testWeatherReport() {
        WeatherDemo testMockito = Mockito.mock(WeatherDemo.class);
        WeatherDemo testJUnit = new WeatherDemo();

        when(testMockito.getHighTemp()).thenReturn(90);
        assertEquals("hot", testJUnit.getForecast());
    }
}

=============================================== =================================

基本上,我想在getForecast()上运行一个JUnit。它会返回冷,温或热,具体取决于当天的高温。要获得高温,它会调用getHighTemp()。让我们假设getHighTemp()调用Web服务来获取温度(我仅为测试目的硬编码一个值)。因为这是一个外部资源,我的Junit没有通过隔离测试,根本不是一个单元测试。更不用说getHighTemp()每次调用时都不会返回相同的值。

因此,我想模拟getHighTemp(),告诉它总是返回90的临时值。

Mockito测试从testWeatherReport()运行。这是我被困的地方。我可以在执行此操作时模拟getHighTemp()返回90:

当(testMockito.getHighTemp())thenReturn(90);

但是,当从getForecast()调用时,我无法让它返回90。断言得到了冷却&#34;因为它拿起了32而不是90.

Mockito背后的整个想法是不是我可以模拟一个方法并告诉它确切要返回什么,以便消除外部依赖?如果从getForecast()调用getHighTemp()不会返回90,我就不会看到Mockito的目的。我在这里错过了什么?感谢您的帮助和启发。

比尔

2 个答案:

答案 0 :(得分:3)

你实质上是在问'我怎么能在我的课上模拟一种方法,同时测试其余的'。可以使用Mockito - 在文档中搜索“部分模拟”。然而,(几乎)总是表明您的代码结构错误并需要重构。如果您正在测试访问要模拟的接口的类,那么这表明您应该将接口声明为interface,然后将实现传递给类。这有两个作用:首先它允许您在不改变界面的情况下更改实现;其次,它使课堂可以测试。

所以在你的情况下:

public interface TempSupplier {
    int getHighTemp();
    int getLowTemp();
}

public class WeatherDescriber {
    private final TempSupplier tempSupplier;

    public WeatherDescriber(TempSupplier tempSupplier) {
        this.tempSupplier = tempSupplier;
    }

    public String getForecast() {
        int highTemp = tempSupplier.getHighTemp();
        ...
    }
}

@Test
public void testForecast() {
    TempSupplier supplier = mock(TempSupplier.class);
    when(supplier.getHighTemp()).thenReturn(90);
    WeatherDescriber describer = new WeatherDescriber(supplier);
    assertThat(describer.getForecast(), is("Hot"));
}

我通常将模拟拆分为单独的方法,以便您可以轻松测试:

private WeatherDescriber getDescriber(int lowTemp, int highTemp) {
    TempSupplier supplier = mock(TempSupplier.class);
    when(supplier.getLowTemp()).thenReturn(lowTemp);
    when(supplier.getHighTemp()).thenReturn(highTemp);
    return new WeatherDescriber(supplier);
}

@Test
public void testDescribeVariousTemps() {
    assertThat(getDescriber(10, 20).getForecast(), is("cold"));
    assertThat(getDescriber(30, 35).getForecast(), is("cold"));
    assertThat(getDescriber(40, 45).getForecast(), is("warmer"));
    assertThat(getDescriber(90, 130).getForecast(), is("melting"));
} 

答案 1 :(得分:1)

在您的示例中,testMockitotestJUnit是不同的对象 - 您已在testMockito上模拟了该方法,但testJUnit已实际使用该方法,返回32。

您可能需要考虑将代码分解为两个类 - 然后您可以使getHighTemp()中的代码成为主类的依赖项。这是您在测试中提供模拟以及实际运行代码的实际实现的地方。 (可能是某种Dependency Injection

以这种方式构建的代码似乎遵循Single Responsibility Principle - 这样做的好处是,如果您需要更改天气数据供应商,它会更容易一些。当你说&#34时,我认为你仍然沿着这些方向前进;因为这是一个外部资源,我的Junit没有通过隔离测试#34;。