Java 使用 Mockito 模拟静态方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21105403/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Mocking static methods with Mockito
提问by Naftuli Kay
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. I'm using JUnit 4 and Mockito for my test cases. Is there a good way to mock/verify this specific use-case?
我想验证传递给 的参数DriverManager.getConnection
,但我不知道如何模拟静态方法。我在测试用例中使用 JUnit 4 和 Mockito。是否有模拟/验证此特定用例的好方法?
采纳答案by MariuszS
Use PowerMockitoon 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:
更多信息:
回答by marek.kapowicki
To mock static method you should use a Powermock look at: https://github.com/powermock/powermock/wiki/MockStatic. Mockito doesn't providethis functionality.
要模拟静态方法,您应该使用 Powermock 查看:https: //github.com/powermock/powermock/wiki/MockStatic。Mockito不提供此功能。
You can read nice a article about mockito: http://refcardz.dzone.com/refcardz/mockito
你可以读一篇关于 mockito 的文章:http://refcardz.dzone.com/refcardz/mockito
回答by ChrisM
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 创建一个接口,模拟这个接口,通过某种依赖注入注入它并在该模拟上进行验证。
回答by 99Sono
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. 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.
如果您正在使用 CDI 并且可以使用 @Inject 注释,那么它会更容易。只需制作您的 Wrapper bean @ApplicationScoped,将那个东西作为合作者注入(您甚至不需要凌乱的构造函数来进行测试),然后继续模拟。
回答by Fermin Silva
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);
}
}
回答by 6324
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.
我有一个类似的问题。接受的答案对我不起作用,直到我做出更改: @PrepareForTest(TheClassThatContainsStaticMethod.class)
根据PowerMock 的 mockStatic 文档。
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());
}
}
回答by some random guy
Observation : When you call static method within a static entity, you need to change the class in @PrepareForTest.
观察:当您在静态实体中调用静态方法时,您需要更改@PrepareForTest 中的类。
For e.g. :
例如:
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());
回答by iirekm
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(...);
回答by David Miguel
Mockito cannot capture static methods, but since Mockito 2.14.0you 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. (source)
他们的目标不是直接支持静态模拟,而是改进其公共 API,以便其他库(如Powermockito)不必依赖内部 API 或直接复制某些 Mockito 代码。(来源)
Disclaimer: Mockito team thinks that the road to hell is paved with static methods. However, Mockito's job is not to protect your code from static methods. If you don't like your team doing static mocking, stop using Powermockito in your organization. Mockito needs to evolve as a toolkit with an opinionated vision on how Java tests should be written (e.g. don't mock statics!!!). However, Mockito is not dogmatic. We don't want to block unrecommended use cases like static mocking. It's just not our job.
免责声明:Mockito 团队认为通往地狱的道路是由静态方法铺就的。但是,Mockito 的工作不是保护您的代码免受静态方法的影响。如果您不喜欢您的团队进行静态模拟,请停止在您的组织中使用 Powermockito。Mockito 需要发展为一个工具包,对如何编写 Java 测试有自己的看法(例如,不要模拟静态!!!)。然而,Mockito 并不是教条主义的。我们不想阻止不推荐的用例,如静态模拟。这不是我们的工作。
回答by Zlatan
Use JMockit framework. It worked for me. You don't have to write statements for mocking DBConenction.getConnection() method. Just the below code is enough.
使用 JMockit 框架。它对我有用。您不必为模拟 DBConenction.getConnection() 方法编写语句。只需下面的代码就足够了。
@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;
}
};