以下是一份MRE:

package com.example.mockitomre;

public interface DocumentedEndpoint {
    EndpointDetails getDetails();
}
package com.example.mockitomre;

public interface EndpointDetails {
    String getPath();
}
package com.example.mockitomre;

public interface EndpointSieve {
    boolean isAllowed(DocumentedEndpoint endpoint);
}
package com.example.mockitomre;

import org.springframework.util.AntPathMatcher;

public class EndpointSieveConfig {
    public EndpointSieve errorPathEndpointSieve(GatewayMeta gatewayMeta, AntPathMatcher antPathMatcher) {
        return endpoint -> gatewayMeta.getIgnoredPatterns().stream()
                .noneMatch(ignoredPattern -> antPathMatcher.match(ignoredPattern, endpoint.getDetails().getPath()));
    }
}
package com.example.mockitomre;

import lombok.Getter;

import java.util.List;

@Getter
public final class GatewayMeta {
    private List<String> ignoredPatterns;
}
package com.example.mockitomre;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.util.AntPathMatcher;

import java.util.List;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class ErrorPathEndpointSieveTest {
    private final EndpointSieveConfig endpointSieveConfig = new EndpointSieveConfig();
    @Mock
    private GatewayMeta gatewayMetaMock;
    @Mock
    private AntPathMatcher antPathMatcherMock;
    private EndpointSieve errorPathEndpointSieve;

    @Test
    void doesntAllowIgnoredPatterns() {
        String ignoredPattern = "/ignored-path/**";
        String anotherIgnoredPattern = "/*/another-ignored-path";
        when(gatewayMetaMock.getIgnoredPatterns()).thenReturn(List.of(
                ignoredPattern, anotherIgnoredPattern
        ));

        String pathToExclude = "/ignored-path";
        String anotherPathToExclude = "/it-is/another-ignored-path";

        when(antPathMatcherMock.match(ignoredPattern, pathToExclude)).thenReturn(true);
        when(antPathMatcherMock.match(anotherIgnoredPattern, anotherPathToExclude)).thenReturn(true);

        DocumentedEndpoint endpointToExclude = mock(DocumentedEndpoint.class, RETURNS_DEEP_STUBS);
        when(endpointToExclude.getDetails().getPath()).thenReturn(pathToExclude);

        DocumentedEndpoint anotherEndpointToExclude = mock(DocumentedEndpoint.class, RETURNS_DEEP_STUBS);
        when(anotherEndpointToExclude.getDetails().getPath()).thenReturn(anotherPathToExclude);

        String okPath = "/another-ignored-path/on-second-thought-it-is-not";
        DocumentedEndpoint endpointToKeep = mock(DocumentedEndpoint.class, RETURNS_DEEP_STUBS);
        when(endpointToKeep.getDetails().getPath()).thenReturn(okPath);

        errorPathEndpointSieve = endpointSieveConfig.errorPathEndpointSieve(gatewayMetaMock, antPathMatcherMock);

        assertThat(errorPathEndpointSieve.isAllowed(endpointToExclude)).isFalse();
        assertThat(errorPathEndpointSieve.isAllowed(anotherEndpointToExclude)).isFalse();

        assertThat(errorPathEndpointSieve.isAllowed(endpointToKeep)).isTrue();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>mockito-mre</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mockito-mre</name>
    <description>mockito-mre</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

所以问题来了:一旦我运行了测试,我就会得到

org.mockito.exceptions.misusing.PotentialStubbingProblem: 
Strict stubbing argument mismatch. Please check:
 - this invocation of 'match' method:
    antPathMatcherMock.match(
    "/ignored-path/**",
    "/it-is/another-ignored-path"
);
    -> at com.example.mockitomre.EndpointSieveConfig.lambda$errorPathEndpointSieve$0(EndpointSieveConfig.java:8)
 - has following stubbing(s) with different arguments:
    1. antPathMatcherMock.match(
    "/*/another-ignored-path",
    "/it-is/another-ignored-path"
);
      -> at com.example.mockitomre.ErrorPathEndpointSieveTest.doesntAllowIgnoredPatterns(ErrorPathEndpointSieveTest.java:37)
Typically, stubbing argument mismatch indicates user mistake when writing tests.
Mockito fails early so that you can debug potential problem easily.
However, there are legit scenarios when this exception generates false negative signal:
  - stubbing the same method multiple times using 'given().will()' or 'when().then()' API
    Please use 'will().given()' or 'doReturn().when()' API for stubbing.
  - stubbed method is intentionally invoked with different arguments by code under test
    Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
For more information see javadoc for PotentialStubbingProblem class.

    at org.springframework.util.AntPathMatcher.match(AntPathMatcher.java:195)
    at com.example.mockitomre.EndpointSieveConfig.lambda$errorPathEndpointSieve$0(EndpointSieveConfig.java:8)

我不知道Mockito到底想从我这里得到什么,但我做了一些实验,这不是关于

  1. 用不同的论据嘲弄相同的方法;
  2. 事实上,该方法也是使用任何存根过程中不涉及的参数集调用的.
// here, Mockito is fine with calling match("/ignored-path/**", "/it-is/another-ignored-path")

    @Test
    void doesntAllowIgnoredPatterns() {
        when(antPathMatcherMock.match("/ignored-path/**", "/ignored-path")).thenReturn(true);
        when(antPathMatcherMock.match("/*/another-ignored-path", "/it-is/another-ignored-path")).thenReturn(true);

        antPathMatcherMock.match("/ignored-path/**", "/ignored-path");
        antPathMatcherMock.match("/*/another-ignored-path", "/it-is/another-ignored-path");

        antPathMatcherMock.match("/ignored-path/**", "/it-is/another-ignored-path");
    }

你知道有什么帮助(除了放弃Mockito的扩展,我讨厌它,它带来了更多的问题,它解决了)?使EndpointSieveConfig成为ErrorPathEndpointSieveTest的嵌套类,如下所示:

package com.example.mockitomre;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.util.AntPathMatcher;

import java.util.List;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class ErrorPathEndpointSieveTest {
    private final EndpointSieveConfig endpointSieveConfig = new EndpointSieveConfig();
    @Mock
    private GatewayMeta gatewayMetaMock;
    @Mock
    private AntPathMatcher antPathMatcherMock;
    private EndpointSieve errorPathEndpointSieve;

    @Test
    void doesntAllowIgnoredPatterns() {
        String ignoredPattern = "/ignored-path/**";
        String anotherIgnoredPattern = "/*/another-ignored-path";
        when(gatewayMetaMock.getIgnoredPatterns()).thenReturn(List.of(
                ignoredPattern, anotherIgnoredPattern
        ));

        String pathToExclude = "/ignored-path";
        String anotherPathToExclude = "/it-is/another-ignored-path";

        when(antPathMatcherMock.match(ignoredPattern, pathToExclude)).thenReturn(true);
        when(antPathMatcherMock.match(anotherIgnoredPattern, anotherPathToExclude)).thenReturn(true);

        DocumentedEndpoint endpointToExclude = mock(DocumentedEndpoint.class, RETURNS_DEEP_STUBS);
        when(endpointToExclude.getDetails().getPath()).thenReturn(pathToExclude);

        DocumentedEndpoint anotherEndpointToExclude = mock(DocumentedEndpoint.class, RETURNS_DEEP_STUBS);
        when(anotherEndpointToExclude.getDetails().getPath()).thenReturn(anotherPathToExclude);

        String okPath = "/another-ignored-path/on-second-thought-it-is-not";
        DocumentedEndpoint endpointToKeep = mock(DocumentedEndpoint.class, RETURNS_DEEP_STUBS);
        when(endpointToKeep.getDetails().getPath()).thenReturn(okPath);

        errorPathEndpointSieve = endpointSieveConfig.errorPathEndpointSieve(gatewayMetaMock, antPathMatcherMock);

        assertThat(errorPathEndpointSieve.isAllowed(endpointToExclude)).isFalse();
        assertThat(errorPathEndpointSieve.isAllowed(anotherEndpointToExclude)).isFalse();

        assertThat(errorPathEndpointSieve.isAllowed(endpointToKeep)).isTrue();
    }

    public class EndpointSieveConfig {
        public EndpointSieve errorPathEndpointSieve(GatewayMeta gatewayMeta, AntPathMatcher antPathMatcher) {
            return endpoint -> gatewayMeta.getIgnoredPatterns().stream()
                    .noneMatch(ignoredPattern -> antPathMatcher.match(ignoredPattern, endpoint.getDetails().getPath()));
        }
    }
}

现在它过go 了!这些问题包括:

  1. 莫奇托想从我这里得到什么?
  2. 为什么做出这种奇怪的改变会让Mockito高兴呢?

不,this question不管用

Anything that doesn't address moving the class is not an answer to this question. Stop the "duplicate" nonsense please and actually read the question

对于那些建议Javadoc人的人:这不是一个可靠的信息来源.我确实从那里粘贴了代码,它doesn't触发PotentialStubbingProblem,它触发UnnecessaryStubbingException

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.mockito.BDDMockito.given;

@ExtendWith(MockitoExtension.class)
public class MockitoTest {
    @Mock
    SomeClass mock;
    @Test
    void test() {
        //test method:
        Something something = new Something();
        given(mock.getSomething(100)).willReturn(something);

        //code under test:
        Something something2 = mock.getSomething(50); // <-- stubbing argument mismatch
    }

    abstract class SomeClass {
        abstract Something getSomething(int arg);
    }

    class Something {}
}

推荐答案

首先,让我们就术语Strictness in Mockito #769达成一致

Mockito中的严格可以有两种形式:

  • 严格的存根,要求实际使用所有声明的存根
  • 严格的模拟,要求在调用所有非空方法之前将其存根

future 方向:

  • 默认情况下启用严格的存根,可 Select 退出
  • 默认情况下严格模拟关闭,可选

在您的情况下,我们要求谈论严格的拔桩行为.

在DefaultMockitoSessionBuilder中隐式地打开了严格禁止:

Strictness effectiveStrictness = this.strictness == null ? Strictness.STRICT_STUBS : this.strictness;

让我们为2个模拟显式地启用Strong_Stubs:

  • 通过@Mock和MockitoExtension创建的
  • 通过Mockito.mock创建的一个
@Mock(strictness = Mock.Strictness.STRICT_STUBS)
private PathMatcher pathMatcherMock;

private PathMatcher pathMatcherMock = Mockito.mock(PathMatcher.class, withSettings().strictness(org.mockito.quality.Strictness.STRICT_STUBS));

我们的目标是让模拟尽可能相似--没有隐式默认设置.

Problem 1: Mockito.mock version does not throw PotentialStubbingProblem

对模拟人的CreationSettings.stubbingLookupListeners进行判断后发现,它们是空的.DefaultStubbingLookupListener是负责抛出PotentialStubbingProblem的侦听器,其代码永远不会执行.

我觉得这令人困惑,而且文档也很少(可能是个错误?).

Problem 2: @Mock version throws PotentialStubbingProblem, but moving of code to test file resolves PotentialStubbingProblem

对模拟CreationSettings.stubbingLookupListeners的判断显示,它们含有DefaultStubbingLookupListener

负责此行为的代码再次出现在DefaultStubbingLookupListener

private static List<Invocation> potentialArgMismatches(
        Invocation invocation, Collection<Stubbing> stubbings) {
    List<Invocation> matchingStubbings = new LinkedList<>();
    for (Stubbing s : stubbings) {
        if (UnusedStubbingReporting.shouldBeReported(s)
                && Objects.equals(
                        s.getInvocation().getMethod().getName(),
                        invocation.getMethod().getName())
                // If stubbing and invocation are in the same source file we assume they are in
                // the test code,
                // and we don't flag it as mismatch:
                && !Objects.equals(
                        s.getInvocation().getLocation().getSourceFile(),
                        invocation.getLocation().getSourceFile())) {
            matchingStubbings.add(s.getInvocation());
        }
    }
    return matchingStubbings;
}

Follow up:

rationale for strict stubbing

请阅读:

再次,让我们区分严格的模仿和严格的模仿.

Your test

在你的测试中,你会遇到这样的情况,你不想要严格的答案--你想要的是没有明确提到的情况的默认答案.在这种情况下,你应该使用宽大的mock.

我会争辩说,模仿AntPathMatcher根本不是一个很好的解决方案--我会用真正的类来代替. AntPathMatcher的行为被很好地定义,并且类很容易实例化.通过模拟,您可以很容易地创建真实AntPathMatcher匹配的情况,但模拟不能.

使用带有PathMatcher的模拟不会那么令人困惑,但我仍然建议使用真正的类.

No strict stubbing errors in test file

这令人困惑,而且文档也很少,但不幸的是,这是必要的.

与其他一些框架一样,Mockito没有用于存根的记录重放阶段.存根在模拟对象上进行实际调用.

如果没有这个条件,

interface Foo {
    String myMethod(String x);
}

@ExtendWith(MockitoExtension.class)
public class FooTest {

    @Mock(strictness = Mock.Strictness.STRICT_STUBS)
    Foo foo;

    @Test
    void testFoo() {
        Mockito.when(foo.myMethod("a")).thenReturn("1");
        Mockito.when(foo.myMethod("b")).thenReturn("2");

        System.out.println(foo.myMethod("a"));
        System.out.println(foo.myMethod("b"));
    }
}

以上代码运行正常,但如果删除源文件条件,则在第一次调用foo.myMethod("b")时会抛出PotentialStubbingProblem.

Mockito.mock version does not throw PotentialStubbingProblem

我筹集了MockSettings.strictness() not observed #3262美元--我想这是个错误

Java相关问答推荐

虚拟线程似乎在外部服务调用时阻止运营商线程

Java事件系统通用转换为有界通配符

无法找到符号错误—Java—封装

替换com. sun. jndi. dns. DnsContextFactory Wildfly23 JDK 17

在Java Stream上调用collect方法出现意外结果

有关手动创建的包的问题

需要一个找不到的jakarta.sistence.EntityManager类型的Bean

如何让JFileChooser(DIRECTORIES_ONLY)从FolderName中的空白开始?

使用Testcontainers与OpenLiberty Server进行集成测试会抛出SocketException

Chunk(Int)已弃用并标记为要删除

当返回Mono<;Something>;时,不会调用Mono<;void>;.flatMap

通过移动一个类解决了潜在的StubbingProblem.它怎麽工作?

搜索列表返回多个频道

在VS代码中,如何启用Java Main函数的&Q;Run|DEBUG&Q;代码?

Dijkstra搜索算法的实现

IntelliJ IDEA依赖项工具窗口丢失

如何在Java中使用正则表达式拆分字符串

在Java中将对象&转换为&q;HashMap(&Q)

OpenAPI Maven插件生成错误的Java接口名称

单例模式中热切初始化和惰性初始化的区别