Ich habe eine Fabrik geschrieben, um Java.sql.Connection
-Objekte zu produzieren:
public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {
@Override public Connection getConnection() {
try {
return DriverManager.getConnection(...);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
Ich möchte die an DriverManager.getConnection
übergebenen Parameter überprüfen, weiß jedoch nicht, wie eine statische Methode gemustert wird. Ich verwende JUnit 4 und Mockito für meine Testfälle. Gibt es eine gute Möglichkeit, diesen speziellen Anwendungsfall zu verspotten/zu überprüfen?
Verwenden Sie PowerMockito auf Mockito.
Beispielcode:
@RunWith(PowerMockRunner.class)
@PrepareForTest(DriverManager.class)
public class Mocker {
@Test
public void testName() throws Exception {
//given
PowerMockito.mockStatic(DriverManager.class);
BDDMockito.given(DriverManager.getConnection(...)).willReturn(...);
//when
sut.execute();
//then
PowerMockito.verifyStatic();
DriverManager.getConnection(...);
}
Mehr Informationen:
Die typische Strategie, um statischen Methoden auszuweichen, die Sie nicht vermeiden können, besteht darin, umwickelte Objekte zu erstellen und stattdessen die Wrapper-Objekte zu verwenden.
Die Wrapper-Objekte werden zu Fassaden der realen statischen Klassen, und Sie testen diese nicht.
Ein Wrapper-Objekt könnte ungefähr so aussehen
public class Slf4jMdcWrapper {
public static final Slf4jMdcWrapper SINGLETON = new Slf4jMdcWrapper();
public String myApisToTheSaticMethodsInSlf4jMdcStaticUtilityClass() {
return MDC.getWhateverIWant();
}
}
Schließlich kann Ihre getestete Klasse dieses Singleton-Objekt verwenden, indem Sie zum Beispiel Einen Standardkonstruktor für die Verwendung im realen Leben haben:
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;
}
}
Und hier haben Sie eine Klasse, die leicht getestet werden kann, da Sie eine Klasse nicht direkt mit statischen Methoden verwenden.
Wenn Sie CDI verwenden und die @Inject-Annotation verwenden können, ist es noch einfacher. __ Machen Sie Ihre Wrapper-Bean zu @ApplicationScoped, lassen Sie das Ding als Collaborator injizieren (Sie benötigen nicht einmal chaotische Konstruktoren zum Testen), und mach weiter mit dem Spott.
Wie bereits erwähnt, können Sie statische Methoden nicht mit Mockito simulieren.
Wenn das Ändern Ihres Testframeworks keine Option ist, können Sie Folgendes tun:
Erstellen Sie eine Schnittstelle für DriverManager, simulieren Sie diese Schnittstelle, fügen Sie sie über eine Art Abhängigkeitseinspritzung ein und überprüfen Sie diese Einstellung.
Ich hatte ein ähnliches Problem. Die akzeptierte Antwort hat für mich nicht funktioniert, bis ich die Änderung vorgenommen habe: @PrepareForTest(TheClassThatContainsStaticMethod.class)
laut PowerMocks Dokumentation für mockStatic .
Und ich muss nicht BDDMockito
verwenden.
Meine Klasse:
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();
}
}
}
Meine Testklasse:
@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());
}
}
Beobachtung: Wenn Sie eine statische Methode innerhalb einer statischen Entität aufrufen, müssen Sie die Klasse in @PrepareForTest ändern.
Für z. :
securityAlgo = MessageDigest.getInstance(SECURITY_ALGORITHM);
Verwenden Sie für den obigen Code, wenn Sie die MessageDigest-Klasse simulieren müssen
@PrepareForTest(MessageDigest.class)
Während, wenn Sie etwas wie unten haben:
public class CustomObjectRule {
object = DatatypeConverter.printHexBinary(MessageDigest.getInstance(SECURITY_ALGORITHM)
.digest(message.getBytes(ENCODING)));
}
dann müssen Sie die Klasse vorbereiten, in der sich der Code befindet.
@PrepareForTest(CustomObjectRule.class)
Und dann verspotten Sie die Methode:
PowerMockito.mockStatic(MessageDigest.class);
PowerMockito.when(MessageDigest.getInstance(Mockito.anyString()))
.thenThrow(new RuntimeException());
Um die statische Methode zu simulieren, sollten Sie einen Powermock-Look verwenden: https://github.com/powermock/powermock/wiki/MockStatic . Mockito bietet diese Funktion nicht.
Sie können Nizza einen Artikel über Mockito lesen: http://refcardz.dzone.com/refcardz/mockito
Sie können es mit ein bisschen Refactoring machen:
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...);
}
}
Dann können Sie Ihre Klasse MySQLDatabaseConnectionFactory
erweitern, um eine gespielte Verbindung zurückzugeben, Assertions zu den Parametern vorzunehmen, usw.
Die erweiterte Klasse kann sich innerhalb des Testfalls befinden, wenn sie sich in demselben Paket befindet (was ich Ihnen empfehlen sollte).
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);
}
}
Ich habe auch eine Kombination aus Mockito und AspectJ geschrieben: https://github.com/iirekm/varia/tree/develop/ajmock
Ihr Beispiel wird:
when(() -> DriverManager.getConnection(...)).thenReturn(...);
Mockito kann keine statischen Methoden erfassen, aber seit Mockito 2.14.0 können Sie es simulieren, indem Sie Aufrufinstanzen statischer Methoden erstellen.
Beispiel (extrahiert aus ihren 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;
}
}
}
Ihr Ziel ist nicht die direkte Unterstützung von statischem Mocking, sondern die Verbesserung der öffentlichen APIs, sodass andere Bibliotheken wie Powermockito nicht auf interne APIs angewiesen sind oder Mockito-Code direkt duplizieren müssen. ( Quelle )
Haftungsausschluss: Das Mockito-Team meint, dass der Weg zur Hölle mit statischen Methoden gepflastert ist. Mockito hat jedoch nicht die Aufgabe, Ihren Code vor statischen Methoden zu schützen. Wenn Sie nicht möchten, dass Ihr Team statische Verspottungen durchführt, beenden Sie die Verwendung von Powermockito in Ihrer Organisation. Mockito muss sich als Toolkit mit einer meinungsfähigen Vision darüber entwickeln, wie Java-Tests geschrieben werden sollten (z. B. keine statischen Darstellungen !!!). Mockito ist jedoch nicht dogmatisch. Wir möchten nicht empfohlene Anwendungsfälle wie statisches Mocking nicht blockieren. Es ist einfach nicht unser Job.
JMockit Framework verwenden . Es hat für mich funktioniert. Sie müssen keine Anweisungen schreiben, um die DBConenction.getConnection () - Methode zu verspotten. Nur der unten stehende Code reicht aus.
@Mock unten ist das Paket 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;
}
};