为什么 Mockito 3.3.0 在所有必要的时候都会引发 UnnecessaryStubbingException?

发布于 2025-01-12 17:10:55 字数 1334 浏览 1 评论 0原文

给定以下类:

    class Parent {
        Child getChild() {
            return null;
        }
    }

    class Child {
        List<String> getValues(String param) {
            return Collections.emptyList();
        }
    }

以及以下 junit 测试:

    @Test
    public void name() {
        Parent parentMock = mock(Parent.class, RETURNS_DEEP_STUBS);

        when(parentMock.getChild().getValues("foo")).thenReturn(List.of("Something"));
        when(parentMock.getChild().getValues("bar")).thenReturn(List.of("Something"));

        assertThat(parentMock.getChild().getValues("foo")).hasSize(1);
        assertThat(parentMock.getChild().getValues("bar")).hasSize(1);
    }

Mockito 3.3.0(及其严格的存根)引发以下异常:

org.mockito.exceptions.misusing.UnnecessaryStubbingException: 
Unnecessary stubbings detected in test class: ClassTest
Clean & maintainable test code requires zero unnecessary code.
There are 1 unnecessary stubbings (click to navigate to relevant line of code):
  1. -> at ClassTest.name(ClassTest.java:59)

在这种情况下我不明白的是,两个 when() 都是测试中调用。所以我希望 Mockito 不会引发异常。

Mockito 为何抱怨?

- - 编辑 - - 看来这是mockito-core 3.3.0中的一个错误。 升级到最新版本3.12.4修复了该问题。 不确定哪个版本修复了这个bug,(Adriaan说这个bug在3.4.3中没有出现)但是我切换到3.12.4就可以了。

Given the following classes:

    class Parent {
        Child getChild() {
            return null;
        }
    }

    class Child {
        List<String> getValues(String param) {
            return Collections.emptyList();
        }
    }

And the following junit test:

    @Test
    public void name() {
        Parent parentMock = mock(Parent.class, RETURNS_DEEP_STUBS);

        when(parentMock.getChild().getValues("foo")).thenReturn(List.of("Something"));
        when(parentMock.getChild().getValues("bar")).thenReturn(List.of("Something"));

        assertThat(parentMock.getChild().getValues("foo")).hasSize(1);
        assertThat(parentMock.getChild().getValues("bar")).hasSize(1);
    }

Mockito 3.3.0 (with its strict stubbing) raise the following exception:

org.mockito.exceptions.misusing.UnnecessaryStubbingException: 
Unnecessary stubbings detected in test class: ClassTest
Clean & maintainable test code requires zero unnecessary code.
There are 1 unnecessary stubbings (click to navigate to relevant line of code):
  1. -> at ClassTest.name(ClassTest.java:59)

What I don't understand in this case is that, both when() are called in the test. So I expect Mockito not raise the exception.

Why Mockito is complaining ?

--- edit ---
It seems that it is a bug in mockito-core 3.3.0.
Upgrading to the latest version 3.12.4 fix the problem.
Not sure in which version the bug is fixed, (Adriaan said that the bug doesn't occured in 3.4.3) but it's ok for me to switch to the 3.12.4.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

梦幻的心爱 2025-01-19 17:10:55

我刚刚尝试使用这些依赖项:

dependencies {
    implementation 'junit:junit:4.13.2'
    implementation 'org.mockito:mockito-core:3.4.3'
    implementation 'org.assertj:assertj-core:3.22.0'
}

并且没有发生错误。

我建议你检查你的类路径。

关于 RETURNS_DEEP_STUBS 的使用(来自 org.mockito.Mockito#RETURNS_DEEP_STUBS JavaDoc):

警告:定期清洁很少需要此功能
代码!将其留给遗留代码。模拟一个模拟以返回一个模拟,
返回一个模拟,(...),返回一些有意义的提示
违反德米特定律或嘲笑价值对象(众所周知的
反模式)。

有一天我在网上看到的一句好话:每次
模拟返回模拟仙女死亡。

请注意,此答案将返回现有的模拟
与存根匹配。这种行为对于深存根来说是可以的,并且允许
验证在链的最后一个模拟上工作。

它适用于像 Builders 这样的 Fluent API,或者臭名昭著的 Spring WebClient,其中每个调用都会返回 this,请参阅:Baeldung

也许您喜欢非常紧凑的单元测试。我个人希望它们尽可能明确(并且希望可读),但不可否认,这也是一个品味和风格的问题。

我想我会像这样编写你的测试:

    private final Parent parent = mock(Parent.class);
    private final Child child = mock(Child.class);

    @Before
    public void setup() {
        when(parent.getChild()).thenReturn(child);
    }

    @Test
    public void testFoo() {
        // SETUP
        String param = "foo";
        when(child.getValues(param)).thenReturn(List.of("Something"));

        // CALL
        List<String> result = child.getValues(param);

        // VERIFY
        assertThat(result).hasSize(1);
    }

    @Test
    public void testBar() {
        // SETUP
        String param = "bar";
        when(child.getValues(param)).thenReturn(List.of("Something"));

        // CALL
        List<String> result = child.getValues(param);

        // VERIFY
        assertThat(result).hasSize(1);
    }

所以我尝试将存根中不太相关的部分排除在外,无论是在 @Setup 方法中,还是在其他辅助方法中(或者如果需要的话,也可以是类、构建器、工厂) 。这有助于测试用例专注于我想要测试的行为。

我也从不在一个测试用例中进行多次测试,因为如果该用例失败,我希望立即弄清楚原因是什么。

I just tried with these dependencies:

dependencies {
    implementation 'junit:junit:4.13.2'
    implementation 'org.mockito:mockito-core:3.4.3'
    implementation 'org.assertj:assertj-core:3.22.0'
}

And no error occurred.

I suggest you check your classpath.

Regarding your use of RETURNS_DEEP_STUBS (from the org.mockito.Mockito#RETURNS_DEEP_STUBS JavaDoc):

WARNING: This feature should rarely be required for regular clean
code! Leave it for legacy code. Mocking a mock to return a mock, to
return a mock, (...), to return something meaningful hints at
violation of Law of Demeter or mocking a value object (a well known
anti-pattern).

Good quote I've seen one day on the web: every time a
mock returns a mock a fairy dies.

Please note that this answer will return existing mocks that
matches the stub. This behavior is ok with deep stubs and allows
verification to work on the last mock of the chain.

It is intended for fluent API's like Builders, or the infamous Spring WebClient where each call returns this, see: Baeldung.

Maybe you like really compact unit tests. I personally want them to be as explicit (and hopefully readable) as possible, but admittedly that is also a matter of taste and style.

I think I'd write your test something like this:

    private final Parent parent = mock(Parent.class);
    private final Child child = mock(Child.class);

    @Before
    public void setup() {
        when(parent.getChild()).thenReturn(child);
    }

    @Test
    public void testFoo() {
        // SETUP
        String param = "foo";
        when(child.getValues(param)).thenReturn(List.of("Something"));

        // CALL
        List<String> result = child.getValues(param);

        // VERIFY
        assertThat(result).hasSize(1);
    }

    @Test
    public void testBar() {
        // SETUP
        String param = "bar";
        when(child.getValues(param)).thenReturn(List.of("Something"));

        // CALL
        List<String> result = child.getValues(param);

        // VERIFY
        assertThat(result).hasSize(1);
    }

So I try to get the less relevant part of the stubbing out of the way, either in a @Setup method, or other helper methods (or classes, builders, factories if needed). This helps the test case to focus on the behavior that I want to test.

I also never test multiple times in a single test case, because if the case fails I want it to be immediately clear what the cause is.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文