简体   繁体   English

Mocking static 方法与 Mockito

[英]Mocking static methods with Mockito

I've written a factory to produce java.sql.Connection objects:我写了一个工厂来生产java.sql.Connection对象:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

    @Override public Connection getConnection() {
        try {
            return DriverManager.getConnection(...);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

I'd like to validate the parameters passed to DriverManager.getConnection , but I don't know how to mock a static method.我想验证传递给DriverManager.getConnection的参数,但我不知道如何模拟 static 方法。 I'm using JUnit 4 and Mockito for my test cases.我在测试用例中使用 JUnit 4 和 Mockito。 Is there a good way to mock/verify this specific use-case?是否有模拟/验证此特定用例的好方法?

Use PowerMockito on top of Mockito.在 Mockito 之上使用PowerMockito

Example code:示例代码:

@RunWith(PowerMockRunner.class)
@PrepareForTest(DriverManager.class)
public class Mocker {

    @Test
    public void shouldVerifyParameters() throws Exception {

        //given
        PowerMockito.mockStatic(DriverManager.class);
        BDDMockito.given(DriverManager.getConnection(...)).willReturn(...);

        //when
        sut.execute(); // System Under Test (sut)

        //then
        PowerMockito.verifyStatic();
        DriverManager.getConnection(...);

    }

More information:更多信息:

Mocking of static methods in Mockito is possible since Mockito 3.4.0.从 Mockito 3.4.0 开始可以在 Mockito 中模拟静态方法。 For more details see:有关更多详细信息,请参阅:

https://github.com/mockito/mockito/releases/tag/v3.4.0 https://github.com/mockito/mockito/releases/tag/v3.4.0

https://github.com/mockito/mockito/issues/1013 https://github.com/mockito/mockito/issues/1013

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#static_mocks https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#static_mocks

assertEquals("foo", Foo.method());
try (MockedStatic mocked = mockStatic(Foo.class)) {
 mocked.when(Foo::method).thenReturn("bar");
 assertEquals("bar", Foo.method());
 mocked.verify(Foo::method);
}
assertEquals("foo", Foo.method());

In your case, something like this:在你的情况下,是这样的:

  @Test
  public void testStaticMockWithVerification() throws SQLException {
    try (MockedStatic<DriverManager> dummy = Mockito.mockStatic(DriverManager.class)) {
      DatabaseConnectionFactory factory = new MySQLDatabaseConnectionFactory();
      dummy.when(() -> DriverManager.getConnection("arg1", "arg2", "arg3"))
        .thenReturn(new Connection() {/*...*/});

      factory.getConnection();

      dummy.verify(() -> DriverManager.getConnection(eq("arg1"), eq("arg2"), eq("arg3")));
    }
  }

NOTE: mocking STATIC METHODS requires mockito-inline dependency instead of mockito-core.注意:模拟静态方法需要 mockito-inline 依赖而不是 mockito-core。

For JUnit5 also add this:对于 JUnit5,还要添加:

<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-junit-jupiter</artifactId>
  <version>${mockito.version}</version>
  <scope>test</scope>
</dependency>

The typical strategy for dodging static methods that you have no way of avoiding using, is by creating wrapped objects and using the wrapper objects instead.避开您无法避免使用的静态方法的典型策略是创建包装对象并改用包装对象。

The wrapper objects become facades to the real static classes, and you do not test those.包装器对象成为真正的静态类的外观,您无需对其进行测试。

A wrapper object could be something like包装器对象可能类似于

public class Slf4jMdcWrapper {
    public static final Slf4jMdcWrapper SINGLETON = new Slf4jMdcWrapper();

    public String myApisToTheSaticMethodsInSlf4jMdcStaticUtilityClass() {
        return MDC.getWhateverIWant();
    }
}

Finally, your class under test can use this singleton object by, for example, having a default constructor for real life use:最后,您的被测类可以使用这个单例对象,例如,拥有一个用于现实生活的默认构造函数:

public class SomeClassUnderTest {
    final Slf4jMdcWrapper myMockableObject;

    /** constructor used by CDI or whatever real life use case */
    public myClassUnderTestContructor() {
        this.myMockableObject = Slf4jMdcWrapper.SINGLETON;
    }

    /** constructor used in tests*/
    myClassUnderTestContructor(Slf4jMdcWrapper myMock) {
        this.myMockableObject = myMock;
    }
}

And here you have a class that can easily be tested, because you do not directly use a class with static methods.在这里,您有一个可以轻松测试的类,因为您不直接使用具有静态方法的类。

If you are using CDI and can make use of the @Inject annotation then it is even easier.如果您正在使用 CDI 并且可以使用 @Inject 注释,那么它会更容易。 Just make your Wrapper bean @ApplicationScoped, get that thing injected as a collaborator (you do not even need messy constructors for testing), and go on with the mocking.只需让您的 Wrapper bean @ApplicationScoped,将该东西作为协作者注入(您甚至不需要凌乱的构造函数来进行测试),然后继续进行模拟。

I had a similar issue.我有一个类似的问题。 The accepted answer did not work for me, until I made the change: @PrepareForTest(TheClassThatContainsStaticMethod.class) , according to PowerMock's documentation for mockStatic .根据PowerMock 的 mockStatic 文档,在我做出更改之前,接受的答案对我不起作用: @PrepareForTest(TheClassThatContainsStaticMethod.class)

And I don't have to use BDDMockito .而且我不必使用BDDMockito

My class:我的课:

public class SmokeRouteBuilder {
    public static String smokeMessageId() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            log.error("Exception occurred while fetching localhost address", e);
            return UUID.randomUUID().toString();
        }
    }
}

My test class:我的测试课:

@RunWith(PowerMockRunner.class)
@PrepareForTest(SmokeRouteBuilder.class)
public class SmokeRouteBuilderTest {
    @Test
    public void testSmokeMessageId_exception() throws UnknownHostException {
        UUID id = UUID.randomUUID();

        mockStatic(InetAddress.class);
        mockStatic(UUID.class);
        when(InetAddress.getLocalHost()).thenThrow(UnknownHostException.class);
        when(UUID.randomUUID()).thenReturn(id);

        assertEquals(id.toString(), SmokeRouteBuilder.smokeMessageId());
    }
}

As mentioned before you can not mock static methods with mockito.如前所述,您不能使用 mockito 模拟静态方法。

If changing your testing framework is not an option you can do the following:如果更改测试框架不是一种选择,您可以执行以下操作:

Create an interface for DriverManager, mock this interface, inject it via some kind of dependency injection and verify on that mock.为 DriverManager 创建一个接口,模拟这个接口,通过某种依赖注入将其注入并在该模拟上进行验证。

For those who use JUnit 5, Powermock is not an option.对于使用 JUnit 5 的用户,Powermock 不是一个选项。 You'll require the following dependencies to successfully mock a static method with just Mockito.您需要以下依赖项才能使用 Mockito 成功模拟静态方法。

testCompile    group: 'org.mockito', name: 'mockito-core',           version: '3.6.0'
testCompile    group: 'org.mockito', name: 'mockito-junit-jupiter',  version: '3.6.0'
testCompile    group: 'org.mockito', name: 'mockito-inline',         version: '3.6.0'

mockito-junit-jupiter add supports for JUnit 5. mockito-junit-jupiter添加对 JUnit 5 的支持。

And support for mocking static methods is provided by mockito-inline dependency. mockito-inline依赖提供了对模拟静态方法的支持。

Example:例子:

@Test
void returnUtilTest() {
    assertEquals("foo", UtilClass.staticMethod("foo"));

    try (MockedStatic<UtilClass> classMock = mockStatic(UtilClass.class)) {

        classMock.when(() -> UtilClass.staticMethod("foo")).thenReturn("bar");

        assertEquals("bar", UtilClass.staticMethod("foo"));
     }

     assertEquals("foo", UtilClass.staticMethod("foo"));
}

The try-with-resource block is used to make the static mock remains temporary, so it's mocked only within that scope. try-with-resource 块用于使静态模拟保持临时状态,因此仅在该范围内模拟。

When not using a try block, make sure to close the mock, once you are done with the assertions.不使用 try 块时,请确保在完成断言后关闭模拟。

MockedStatic<UtilClass> classMock = mockStatic(UtilClass.class)
classMock.when(() -> UtilClass.staticMethod("foo")).thenReturn("bar");
assertEquals("bar", UtilClass.staticMethod("foo"));
classMock.close();

Mocking void methods:模拟 void 方法:

When mockStatic is called on a class, all the static void methods in that class automatically get mocked to doNothing() .当在一个类上调用mockStatic时,该类中的所有静态 void 方法都会自动模拟为doNothing()

Observation : When you call static method within a static entity, you need to change the class in @PrepareForTest.观察:在静态实体中调用静态方法时,需要更改@PrepareForTest 中的类。

For eg :例如:

securityAlgo = MessageDigest.getInstance(SECURITY_ALGORITHM);

For the above code if you need to mock MessageDigest class, use对于上面的代码,如果您需要模拟 MessageDigest 类,请使用

@PrepareForTest(MessageDigest.class)

While if you have something like below :如果您有以下内容:

public class CustomObjectRule {

    object = DatatypeConverter.printHexBinary(MessageDigest.getInstance(SECURITY_ALGORITHM)
             .digest(message.getBytes(ENCODING)));

}

then, you'd need to prepare the class this code resides in.然后,您需要准备此代码所在的类。

@PrepareForTest(CustomObjectRule.class)

And then mock the method :然后模拟该方法:

PowerMockito.mockStatic(MessageDigest.class);
PowerMockito.when(MessageDigest.getInstance(Mockito.anyString()))
      .thenThrow(new RuntimeException());

I also wrote a combination of Mockito and AspectJ: https://github.com/iirekm/varia/tree/develop/ajmock我还写了 Mockito 和 AspectJ 的组合: https ://github.com/iirekm/varia/tree/develop/ajmock

Your example becomes:您的示例变为:

when(() -> DriverManager.getConnection(...)).thenReturn(...);

You can do it with a little bit of refactoring:您可以通过一些重构来做到这一点:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

    @Override public Connection getConnection() {
        try {
            return _getConnection(...some params...);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    //method to forward parameters, enabling mocking, extension, etc
    Connection _getConnection(...some params...) throws SQLException {
        return DriverManager.getConnection(...some params...);
    }
}

Then you can extend your class MySQLDatabaseConnectionFactory to return a mocked connection, do assertions on the parameters, etc.然后你可以扩展你的类MySQLDatabaseConnectionFactory以返回一个模拟连接,对参数进行断言等。

The extended class can reside within the test case, if it's located in the same package (which I encourage you to do)扩展类可以驻留在测试用例中,如果它位于同一个包中(我鼓励你这样做)

public class MockedConnectionFactory extends MySQLDatabaseConnectionFactory {

    Connection _getConnection(...some params...) throws SQLException {
        if (some param != something) throw new InvalidParameterException();

        //consider mocking some methods with when(yourMock.something()).thenReturn(value)
        return Mockito.mock(Connection.class);
    }
}

Mockito cannot capture static methods, but since Mockito 2.14.0 you can simulate it by creating invocation instances of static methods. Mockito 无法捕获静态方法,但从Mockito 2.14.0 开始,您可以通过创建静态方法的调用实例来模拟它。

Example (extracted from their tests ):示例(从他们的测试中提取):

public class StaticMockingExperimentTest extends TestBase {

    Foo mock = Mockito.mock(Foo.class);
    MockHandler handler = Mockito.mockingDetails(mock).getMockHandler();
    Method staticMethod;
    InvocationFactory.RealMethodBehavior realMethod = new InvocationFactory.RealMethodBehavior() {
        @Override
        public Object call() throws Throwable {
            return null;
        }
    };

    @Before
    public void before() throws Throwable {
        staticMethod = Foo.class.getDeclaredMethod("staticMethod", String.class);
    }

    @Test
    public void verify_static_method() throws Throwable {
        //register staticMethod call on mock
        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "some arg");
        handler.handle(invocation);

        //verify staticMethod on mock
        //Mockito cannot capture static methods so we will simulate this scenario in 3 steps:
        //1. Call standard 'verify' method. Internally, it will add verificationMode to the thread local state.
        //  Effectively, we indicate to Mockito that right now we are about to verify a method call on this mock.
        verify(mock);
        //2. Create the invocation instance using the new public API
        //  Mockito cannot capture static methods but we can create an invocation instance of that static invocation
        Invocation verification = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "some arg");
        //3. Make Mockito handle the static method invocation
        //  Mockito will find verification mode in thread local state and will try verify the invocation
        handler.handle(verification);

        //verify zero times, method with different argument
        verify(mock, times(0));
        Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "different arg");
        handler.handle(differentArg);
    }

    @Test
    public void stubbing_static_method() throws Throwable {
        //register staticMethod call on mock
        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "foo");
        handler.handle(invocation);

        //register stubbing
        when(null).thenReturn("hey");

        //validate stubbed return value
        assertEquals("hey", handler.handle(invocation));
        assertEquals("hey", handler.handle(invocation));

        //default null value is returned if invoked with different argument
        Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "different arg");
        assertEquals(null, handler.handle(differentArg));
    }

    static class Foo {

        private final String arg;

        public Foo(String arg) {
            this.arg = arg;
        }

        public static String staticMethod(String arg) {
            return "";
        }

        @Override
        public String toString() {
            return "foo:" + arg;
        }
    }
}

Their goal is not to directly support static mocking, but to improve its public APIs so that other libraries, like Powermockito , don't have to rely on internal APIs or directly have to duplicate some Mockito code.他们的目标不是直接支持静态模拟,而是改进其公共 API,以便其他库(如Powermockito )不必依赖内部 API 或直接复制一些 Mockito 代码。 ( source ) 来源

Disclaimer: Mockito team thinks that the road to hell is paved with static methods.免责声明:Mockito 团队认为通往地狱的道路是用静态方法铺就的。 However, Mockito's job is not to protect your code from static methods.但是,Mockito 的工作不是保护您的代码免受静态方法的影响。 If you don't like your team doing static mocking, stop using Powermockito in your organization.如果您不喜欢您的团队进行静态模拟,请停止在您的组织中使用 Powermockito。 Mockito needs to evolve as a toolkit with an opinionated vision on how Java tests should be written (eg don't mock statics!!!). Mockito 需要发展为一个工具包,对应该如何编写 Java 测试有一个固执的愿景(例如,不要模拟静态!!!)。 However, Mockito is not dogmatic.然而,Mockito 并不是教条主义的。 We don't want to block unrecommended use cases like static mocking.我们不想阻止不推荐的用例,例如静态模拟。 It's just not our job.这不是我们的工作。

To mock static method you should use a Powermock look at: https://github.com/powermock/powermock/wiki/MockStatic .要模拟静态方法,您应该使用 Powermock 查看: https ://github.com/powermock/powermock/wiki/MockStatic。 Mockito doesn't provide this functionality. Mockito 不提供此功能。

You can read nice a article about mockito: http://refcardz.dzone.com/refcardz/mockito您可以阅读一篇关于 mockito 的精彩文章: http ://refcardz.dzone.com/refcardz/mockito

I found one solution in Mockito.我在 Mockito 中找到了一种解决方案。 This feature comes with a version only from 3.4.0此功能仅来自3.4.0的版本

https://asolntsev.github.io/en/2020/07/11/mockito-static-methods/ https://asolntsev.github.io/en/2020/07/11/mockito-static-methods/

  • dependency依赖

    In your build.gradle replace mockito-core:3.3.3 by mockito-inline:3.4.0:在您的 build.gradle 中,将 mockito-core:3.3.3 替换为 mockito-inline:3.4.0:

     testImplementation('org.mockito:mockito-inline:3.4.0')
  • what are we going to mock我们要嘲笑什么

     class Buddy { static String name() { return "John"; } }
  • Mock the static method模拟静态方法

     @Test void lookMomICanMockStaticMethods() { assertThat(Buddy.name()).isEqualTo("John"); try (MockedStatic<Buddy> theMock = Mockito.mockStatic(Buddy.class)) { theMock.when(Buddy::name).thenReturn("Rafael"); assertThat(Buddy.name()).isEqualTo("Rafael"); } assertThat(Buddy.name()).isEqualTo("John"); }

I think this could help us.我认为这可以帮助我们。

Since that method is static, it already has everything you need to use it, so it defeats the purpose of mocking.由于该方法是静态的,因此它已经拥有您使用它所需的一切,因此它违背了模拟的目的。 Mocking the static methods is considered to be a bad practice.模拟静态方法被认为是一种不好的做法。

If you try to do that, it means there is something wrong with the way you want to perform testing.如果您尝试这样做,则意味着您执行测试的方式有问题。

Of course you can use PowerMockito or any other framework capable of doing that, but try to rethink your approach.当然,您可以使用 PowerMockito 或任何其他能够做到这一点的框架,但请尝试重新考虑您的方法。

For example: try to mock/provide the objects, which that static method consumes instead.例如:尝试模拟/提供该静态方法使用的对象。

When you try to mock the static method, you have to write the test inside the try block.当您尝试模拟静态方法时,您必须在 try 块内编写测试。 because It's important to note that scoped mocks must be closed by the entity that activates the mock.因为重要的是要注意作用域模拟必须由激活模拟的实体关闭。

      try (MockedStatic<Tester> tester = Mockito.mockStatic(Tester.class)) {
            tester.when(() -> Tester.testStatic("Testing..")).thenReturn(mock(ReturnObject.class));
    //Here you have to write the test cases
      }

In the above example, we have to mock the Tester Class testStatic Method with input param as "Testing...".在上面的例子中,我们必须模拟 Tester 类 testStatic 方法,输入参数为“Testing...”。 Here, this method will return a ReturnObject class type object.在这里,该方法将返回一个 ReturnObject 类类型的对象。 Hence we write mockito when chain like above.因此,我们在像上面那样链接时编写 mockito。

Don't forget to add below dependency in your Gradle/maven不要忘记在 Gradle/maven 中添加以下依赖项

    testImplementation 'org.mockito:mockito-inline:4.3.1'

Use JMockit framework .使用 JMockit 框架 It worked for me.它对我有用。 You don't have to write statements for mocking DBConenction.getConnection() method.您不必为模拟 DBConenction.getConnection() 方法编写语句。 Just the below code is enough.只需下面的代码就足够了。

@Mock below is mockit.Mock package @Mock 下面是 mockit.Mock 包

Connection jdbcConnection = Mockito.mock(Connection.class);

MockUp<DBConnection> mockUp = new MockUp<DBConnection>() {

            DBConnection singleton = new DBConnection();

            @Mock
            public DBConnection getInstance() { 
                return singleton;
            }

            @Mock
            public Connection getConnection() {
                return jdbcConnection;
            }
         };

使用 java FunctionalInterface 有一个简单的解决方案,然后将该接口添加为您尝试进行单元测试的类的依赖项。

For mocking static functions i was able to do it that way:对于模拟静态函数,我可以这样做:

  • create a wrapper function in some helper class/object.在一些帮助类/对象中创建一个包装函数。 (using a name variant might be beneficial for keeping things separated and maintainable.) (使用名称变体可能有利于保持事物的分离和可维护性。)
  • use this wrapper in your codes.在你的代码中使用这个包装器。 (Yes, codes need to be realized with testing in mind.) (是的,代码需要在考虑到测试的情况下实现。)
  • mock the wrapper function.模拟包装函数。

wrapper code snippet (not really functional, just for illustration)包装器代码片段(不是真正的功能,仅用于说明)

class myWrapperClass ...
    def myWrapperFunction (...) {
        return theOriginalFunction (...)
    }

of course having multiple such functions accumulated in a single wrapper class might be beneficial in terms of code reuse.当然,在单个包装类中累积多个此类函数可能有利于代码重用。

Here I share my mockito MockStatic solution based on an extension as promised in my answer to leokom's solution.在这里,我根据我对 leokom 解决方案的回答中承诺的扩展来分享我的 mockito MockStatic 解决方案。

So, why does Mockito choose try-with-resources?那么,为什么 Mockito 选择 try-with-resources 呢? Well, simply because they want to keep a tidy ship.好吧,仅仅是因为他们想保持一艘整洁的船。 That is good programming after all.毕竟这是一个很好的编程。 Try-with-resources allows construction with guaranteed calling of the close method. Try-with-resources 允许在保证调用 close 方法的情况下进行构造。 But in JUnit we already have that in BeforeEach and AfterEach.但是在 JUnit 中,我们已经在 BeforeEach 和 AfterEach 中拥有了它。 And one can easily add these for a generic purpose to each test class using an Extension that implements BeforeEachCallback and AfterEachCallback.并且可以使用实现 BeforeEachCallback 和 AfterEachCallback 的扩展轻松地将这些用于通用目的添加到每个测试类。

So much for the theory.理论就这么多。 Let's make a static mock for让我们为

Instant.now()

I started with an annotation to be able to mark the fields in my test class that I want to use as static mocks.我从一个注释开始,以便能够在我的测试类中标记我想用作静态模拟的字段。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface StaticMock {

}

This allows me to create a field in my test class for static mocking that I can easily find in my Extension class.这允许我在我的测试类中创建一个用于静态模拟的字段,我可以在我的扩展类中轻松找到它。

  @StaticMock
  private MockedStatic<Instant> staticInstantMock;

I added the Extension I created to my test class.我将我创建的扩展添加到我的测试类中。 You have two options.你有两个选择。

  1. Create an Extension for this purpose and add it to the class next to MockitoExtension, which you also need.为此目的创建一个 Extension 并将其添加到您还需要的 MockitoExtension 旁边的类中。
  2. Create an Extension and have it inherit from MockitoExtension.创建一个扩展并让它从 MockitoExtension 继承。 Now you can replace MockitoExtension on your test class.现在你可以在你的测试类上替换 MockitoExtension。

I used the latter of the two.我使用了两者中的后者。

@ExtendWith({CompanyMockitoExtension.class})
class MyExtendedTestClass {

Now we need something to be returned for the static when it's called:现在我们需要在调用静态时为它返回一些东西:

  @Mock
  private Instant now;

  staticInstantMock.when(Instant::now).thenReturn(now);

The whole test class:整个测试类:

@ExtendWith({CompanyMockitoExtension.class})
class MyExtendedTestClass {

  @StaticMock
  private MockedStatic<Instant> staticInstantMock;

  @Mock
  private Instant now;

  @Test
  void myTestMethod() {
    staticInstantMock.when(Instant::now).thenReturn(now);

    assertThat(Instant::now).isSameAs(now); // This would normally happen in the class you are testing...
  }
}

Now let's take a look at the Extension class.现在让我们看一下 Extension 类。

import static org.mockito.Mockito.mockStatic;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

public class CompanyMockitoExtension extends MockitoExtension {

  @Override
  public void beforeEach(ExtensionContext context) {
    super.beforeEach(context); // Don't forget to call the super!!
    if (context.getTestInstance().isEmpty()) { // Just to be sure...
      return;
    }
    // Get the unit test instance
    Object testSubject = context.getTestInstance().get();
    initializeStaticMocks(testSubject);
  }

  private void initializeStaticMocks(Object testSubject) {
    // Find all fields that I want to static mock
    List<Field> staticMockFields = ReflectionHelper.getFieldsWithAnnotation(testSubject, StaticMock.class);
    staticMockFields.forEach(field -> initializeStaticMock(field, testSubject));
  }

  private void initializeStaticMock(Field field, Object testSubject) {
    // Get the type of the static mock. It is within the generic MockedStatic<> class type.
    Class<?> typeForStaticMock = (Class<?>) ReflectionHelper.getTypesForGeneric(field)[0];
    try {
      // Now set the field with the mockStatic method of Mockito.
      field.setAccessible(true);
      field.set(testSubject, mockStatic(typeForStaticMock));
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Failed to instantiate Static Mock with type: " + typeForStaticMock.getName());
    }
  }

  @Override
  public void afterEach(ExtensionContext context) {
    super.afterEach(context); // Again, do not forget to call the super.
    if (context.getTestInstance().isEmpty()) {
      return;
    }
    Object testSubject = context.getTestInstance().get();
    closeStaticMocks(testSubject); // Close all static mocks.
  }

  private void closeStaticMocks(Object testSubject) {
    // Again find all fields we annotated
    List<Field> staticMockFields = ReflectionHelper.getFieldsWithAnnotation(testSubject, StaticMock.class);
    staticMockFields.forEach(field -> closeStaticMock(field, testSubject));
  }

  private void closeStaticMock(Field field, Object testSubject) {
    // Get the instance and simply call close.
    MockedStatic<?> mockedStaticInstance = ReflectionHelper.getFieldInstance(field, testSubject, MockedStatic.class);
    mockedStaticInstance.close();
  }
}

The nice thing about this extension is that you can add additional mocking stuff.这个扩展的好处是你可以添加额外的模拟内容。 I added verification of no more interactions on all mocks in the AfterEach.我在 AfterEach 中的所有模拟上添加了不再交互的验证。 This is now automatic when we use this extension.现在,当我们使用此扩展程序时,这是自动的。 I also added similar behavior for construction mocking as for the static mocking.我还为构造模拟添加了与静态模拟类似的行为。

As you see, I made my own reflection helper class.如您所见,我创建了自己的反射助手类。 I know there are some standard reflection helper classes and those might be better.我知道有一些标准的反射助手类,这些可能会更好。 Here is mine for this purpose.这是我的目的。

public class ReflectionHelper {

  public static List<Field> getFieldsWithAnnotation(
      Object testSubject,
      Class<? extends Annotation> annotationType
  ) {
    Class<?> testSubjectClass = testSubject.getClass();

    return Arrays.stream(testSubjectClass.getDeclaredFields())
                 .filter(field -> field.isAnnotationPresent(annotationType))
                 .collect(toUnmodifiableList());
  }

  public static List<Field> getCollectionFields(Object testSubject) {
    Class<?> testSubjectClass = testSubject.getClass();

    return Arrays.stream(testSubjectClass.getDeclaredFields())
                 .filter(field -> Collection.class.isAssignableFrom(field.getType()))
                 .collect(toUnmodifiableList());
  }

  @SuppressWarnings("unchecked")
  public static <T> T getFieldInstance(Field field, Object testSubject, Class<T> type) {
    return (T) getFieldInstance(field, testSubject);
  }

  public static Object getFieldInstance(Field field, Object testSubject) {
    try {
      boolean isStatic = isStatic(field.getModifiers());
      Object context = isStatic ? null : testSubject;
      field.setAccessible(true);
      return field.get(context);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Failed to get instance of field.");
    }
  }

  public static Type[] getTypesForGeneric(Field field) {
    ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
    return parameterizedType.getActualTypeArguments();
  }
}

Refactor it a little bit:稍微重构一下:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {
    ConnectionSupplier connectionSupplier = () -> SupplierDriverManager.getConnection();

    public void setConnSupplier(ConnectionSupplier supplier) {
        this.connectionSupplier = supplier;
    }

    @Override 
    public Connection getConnection() {
        try {
            return connectionSupplier.conn();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    @FunctionalInterface
    interface ConnectionSupplier {
        Connection conn();
    }
}

Then you can use the mockito :然后你可以使用mockito

MySQLDatabaseConnectionFactory.ConnectionSupplier connectionSupplier = mock(MySQLDatabaseConnectionFactory.ConnectionSupplier.class);
when(connectionSupplier.conn()).thenReturn(yourMockObject);
yourConnectionFactory.setConnSupplier(connectionSupplier);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM