Mockito模拟带有参数的新实例调用

时间:2018-12-07 10:43:16

标签: java unit-testing junit mocking mockito

出于学校目的,我正在创建一个使用股票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类是用于访问常规API的库的一部分,因此我无法更改这些类。

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

3 个答案:

答案 0 :(得分: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");

答案 1 :(得分:0)

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在评论中所建议的那样,干净的方法是

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

class YourClass {
   private Supplier<TimeSeries> seriesCreator = new SeriesCreator();

// ...

答案 2 :(得分:0)

主类中的代码正在创建一个 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中模拟此方法,并且在模拟时,它应该返回Mocked 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()样式方法。