如果你想在测试时使用 assertThrows,你应该使用存根还是模拟?

If you want to use assertThrows while testing, should you do that with stubs or mocks?

我有这个方法,当有人试图用值 0 调用它时会抛出 IllegalArgumentException

我想为方法 getFrequentRenterPoints.

编写几个存根和模拟测试 - 例如 -

我无法弄清楚模拟中使用的任何“when”或“verify”语句,所以我将部分模拟和部分存根混合在一起并想出了这个:

@Test
public void methodGetFrequentRenterPointsShouldThrowIllegalArgumentException() {
    //given
    Movie movieMock = mock(Movie.class);
    //when
    movieMock.getFrequentRenterPoints(0);
    //then
    assertThrows(IllegalArgumentException.class, () -> {
        movieMock.getFrequentRenterPoints(0);
    });
}

与其他 Mock 一起使用 class 是否可以,或者如果我想使用 assertThrows 是否应该将其更改为存根?或者我可以将 assertThrows 与模拟一起使用吗?

通常您希望被测试的生产方法抛出错误,而不是模拟或存根。我使用 new Movie().

起草了它

此外,在这种情况下,将调用分成 whenthen 真的没有意义,因为如果 movieMock.getFrequentRenterPoints(0); 抛出,assertThrows(...) 将永远不会执行。

要将 given/when/then 结构与 assertThrows API 一起应用,您可以通过某种方式提取传递的 lambda,但我个人认为这样做没有太大好处。

@Test
public void methodGetFrequentRenterPointsShouldThrowIllegalArgumentException() {
    // given
    Movie movieMock = new Movie();

    // when/then
    assertThrows(IllegalArgumentException.class, () -> {
        movieMock.getFrequentRenterPoints(0);
    });
}

正确。

但我试图从另一个角度来解决这个问题:什么时候使用模拟?这是one of my favourite answers那个问题。

所以在实践中:

说你的代码就像(只是猜测所有业务对象和名称...):

List<RenterPoints> getFrequentRenterPoints(int renterId) {
    if(p <= 0) {
        throw new IllegalArgumentException();
    }
    // this is just the rest of code in which your test does not enter because 
    // of thrown exception
    return somethingToReturn();
}

为此你不需要也不应该在这里模拟任何东西。

但是当事情变得更复杂时,就像您的方法一样:

List<RenterPoints> getFrequentRenterPoints(int renterId) {
    if(p <= 0) {
        throw new IllegalArgumentException();
    }
    // What is this?
    // It is injected in the Movie - say - like
    //
    // @Resource
    // private RenterPointService renterPointService;
    List<RenterPoints> unfiltered = renterPointService.getRenterPoints(renterId);
    return filterToFrequent(unfiltered);
}

现在如果你测试 renterId >= 1 那么这个 renterPointService 你如何实例化它而不得到 NPE?说如果它被注入并需要拉起沉重的框架进行测试或者它需要非常沉重的建设等等?你没有,你嘲笑它。

你正在测试 class Movie 而不是 class RenterPointService 所以你不应该费心去想 RenterPointService 是如何工作的,而是它是什么 returns用在class时Movie仍然:你没有模拟你正在测试的class Movie

假设您正在使用 Mockito 并使用注释 然后将在您的测试中完成模拟 class 如:

@Mock
private RenterPointService renterPointService;
@InjectMocks
private Movie movie;

然后你会模拟 renterPointService 的方法,比如:

when(renterPointService.getRenterPoints(anyInt))
    .thenReturn(someListContaineingMockRenterPointsForThisTest);