【问题标题】:Mockito mocking a new instance call with parametersMockito 用参数模拟一个新的实例调用
【发布时间】:2018-12-07 10:43:16
【问题描述】:

出于学校目的,我正在创建一个使用股票 API 的应用程序。

我正在尝试为获取过去 10 年所有股票数据的方法编写测试。我不想真正获取所有数据,而是想抛出一个异常。

我要测试的方法:

@Override
public List<StockData> getAllTeslaStockData() throws AlphaVantageException {
    List<StockData> stockData;

    AlphaVantageConnector apiConnector = new AlphaVantageConnector(APIKEY, TIMEOUT);
    TimeSeries stockTimeSeries = new TimeSeries(apiConnector);

    try {

        Daily responseDaily = stockTimeSeries.daily("TSLA", OutputSize.FULL);
        stockData = responseDaily.getStockData();

    } catch (AlphaVantageException e) {

        LOGGER.log(Level.SEVERE, "something went wrong: ", e);

        throw e;

    }

    return stockData;
}

stockTimeSeries.daily(....) 调用会抛出 AlphaVantageException。

我已经像这样嘲笑 TimeSeries 类:

TimeSeries stockTimeSeries = mock(TimeSeries.class);

在我的测试类中,我想模拟这个调用,并返回一个异常而不是实际数据。

when(stockTimeSeries.daily("TSLA", OutputSize.FULL)).thenThrow(new AlphaVantageException("No stock data available"));

无论我如何尝试模拟这段代码,它都不会抛出异常。它总是只执行代码,并返回有效的股票数据,而不是像我试图做的那样抛出异常。

我如何模拟这段代码,以便它会抛出我期望的测试异常。

AlphaVantageConnector、TimeSeries 和 Daily 类是用于访问 stock API 的库的一部分,因此我无法更改这些类。

我正在使用 JUnit 4.12 和 Mockito 来尝试实现这一目标。

【问题讨论】:

  • 您是否模拟了实例本身的创建 (new TimeSeries(apiConnector);) 并为其分配了模拟引用“stockTimeSeries”? TimeSeries stockTimeSeries = new TimeSeries(apiConnector);
  • 据我所知,mockito 不允许模拟构造函数,如果我错了,请纠正我@AnkurChrungoo
  • 如果你愿意,还有其他方法可以模拟构造函数。但是,我的问题与您如何期望 Mockito 将“stockTimeSeries”识别为该方法应使用的模拟引用更相关。该方法正在创建自己的实例并使用它,因此每次它都会调用实际方法而不是使用模拟对象。
  • 一个简单的例子就是创建一个方法(在你的类中)返回 TimeSeries 对象,然后在你的 Junit 中模拟该方法。

标签: java unit-testing junit mocking mockito


【解决方案1】:

您可以使用 thenThrow() 方法。下面是例子

@Test(expected = NullPointerException.class)
public void whenConfigNonVoidRetunMethodToThrowEx_thenExIsThrown() {
    MyDictionary dictMock = mock(MyDictionary.class);
    when(dictMock.getMeaning(anyString()))
      .thenThrow(NullPointerException.class);
 
    dictMock.getMeaning("word");

【讨论】:

    【解决方案2】:

    TimeSeries 对象是在方法本身中创建的,因此您无法模拟它 - 模拟旨在模拟成员。

    你能做的就是做类似的事情

    class YourClass {
       private Supplier<TimeSeries> seriesCreator = () -> {
           return new TimeSeries(new AlphaVantageConnector(APIKEY, TIMEOUT));
       }
    

    你用来在你的方法中创建系列

    @Override
    public List<StockData> getAllTeslaStockData() throws AlphaVantageException {
        TimeSeries stockTimeSeries = seriesCreator.get();
    

    现在您可以模拟 Supplier。

    @Mock Supplier<TimeSeries> seriesCreatorMock;
    @InjectMocks MyClass sut;
    

    在你的测试中

    @Test(expected = AlphaVantageException.class)
    void testException() {
        when(seriesCreatorMock.get()).thenThrow(new AlphaVantageException());
        sut.getAllTeslaStockData()
    }
    

    编辑:正如 Angkur 在 cmets 中所建议的,干净的方法是

    class SeriesCreator implements Supplier<TimeSeries> {
        public TimeSeries get() {
           return new TimeSeries(new AlphaVantageConnector(APIKEY, TIMEOUT));
        }
    }
    
    class YourClass {
       private Supplier<TimeSeries> seriesCreator = new SeriesCreator();
    

    // ...

    【讨论】:

    • 正确,但是创建一个返回 TimeSeries 对象的简单方法并模拟它会不会容易得多。这里使用 Supplier 似乎有点多余。
    • @AnkurChrungoo 如果你在MyClass 类中创建了一个你想测试的方法,你就不能真正模拟它。您需要为覆盖该方法的测试创建一个子类,这并不是真正的干净测试。
    • 你应该可以用 .spy() 模拟它
    • @AnkurChrungoo 关键是你会更改你正在测试的类。
    • 是的,确实如此。我只是想说,Supplier 是一种高级方式,但更简单也是可能的。
    【解决方案3】:

    主类中的代码正在创建一个TimeSeries 的新实例,每次调用此方法时都会使用该实例,因此根本不会使用模拟的TimeSeries 对象。

    TimeSeries stockTimeSeries = new TimeSeries(apiConnector);  // --> This is not getting mocked
    
        try {
    
            Daily responseDaily = stockTimeSeries.daily("TSLA", OutputSize.FULL);
            stockData = responseDaily.getStockData();
    
        }
    

    您应该在您的类中创建另一个方法(如果它更好地满足 SOLID 原则,甚至可以创建一个单独的类)返回 TimeSeries 对象。类似的东西:-

    <access modifier> TimeSeries getTimeSeries(...) {
    
    }
    

    然后这个方法应该在 Junit 中被模拟,当被模拟时,它应该返回被模拟的 TimeSeries 引用(在 TimeSeries stockTimeSeries = mock(TimeSeries.class); 中创建)。您需要在主类上使用 .spy()(除非您使用不同的类来创建 TimeSeries 对象)才能模拟特定方法 getTimeSeries() 而不是其他方法。

    MainClass mainObject = Mockito.spy(new MainClass());
    Mockito.when(mainObject.getTimeSeries()).thenReturn(stockTimeSeries);
    

    然后,方法调用stockTimeSeries.daily() 将被您现有的代码实际模拟:

    when(stockTimeSeries.daily("TSLA", OutputSize.FULL)).thenThrow(new AlphaVantageException("No stock data available"));
    

    注意:您还应该考虑在模拟时使用 Mockito API 提供的 .anyString() 样式方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-23
      • 2014-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多