Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce PowerMockExtension for JUnit Jupiter support #1146

Open
wants to merge 1 commit into
base: release/2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ext{
objenesisVersion = "3.2"
javassistVersion = "3.27.0-GA"
junitVersion = "4.12"
junitJupiterVersion = "5.9.2"
junitRulesVersion = "4.8.2"
testngVersion = "6.9.10"
xstreamVersion = "1.4.10"
Expand Down
12 changes: 11 additions & 1 deletion gradle/modules.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,16 @@ project(":powermock-modules:powermock-module-junit4-rule-agent") {
}
}

project(":powermock-modules:powermock-module-junit-jupiter-agent") {
description = "PowerMock support module for JUnit Jupiter extension with Java agent"

dependencies {
compile(project(":powermock-core"))
compile(project(":powermock-modules:powermock-module-javaagent"))
compile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
}
}

project(":powermock-modules:powermock-module-testng-common") {
description = "PowerMock module for TestNG. Common classes"

Expand Down Expand Up @@ -348,4 +358,4 @@ project(":powermock-modules:powermock-module-testng-agent") {
configure(publishableModules) { project ->
apply from: "${gradleScriptDir}/release/publish-jar.gradle"

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package org.powermock.modules.junit.jupiter;

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.powermock.core.MockRepository;
import org.powermock.core.agent.JavaAgentClassRegister;
import org.powermock.core.agent.JavaAgentFrameworkRegister;
import org.powermock.core.agent.JavaAgentFrameworkRegisterFactory;
import org.powermock.modules.agent.PowerMockAgent;
import org.powermock.modules.agent.support.JavaAgentClassRegisterImpl;
import org.powermock.modules.agent.support.PowerMockAgentTestInitializer;
import org.powermock.reflect.Whitebox;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;

public class PowerMockExtension implements BeforeEachCallback, AfterEachCallback {

static {
if (PowerMockExtension.class.getClassLoader() != ClassLoader.getSystemClassLoader()) {
throw new IllegalStateException("PowerMockExtension can only be used with the system classloader but was loaded by " + PowerMockExtension.class.getClassLoader());
}
PowerMockAgent.initializeIfPossible();
}

private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(PowerMockExtension.class.getName());
private static final String KEY = "state";
private static final String ANNOTATION_ENABLER = "org.powermock.api.extension.listener.AnnotationEnabler";

@Override
public void beforeEach(ExtensionContext context) throws Exception {
JavaAgentClassRegister agentClassRegister = new JavaAgentClassRegisterImpl();
PowerMockAgentTestInitializer.initialize(context.getRequiredTestClass(), agentClassRegister);

Object annotationEnabler = loadAnnotationEnableIfPresent();
State state = new State(annotationEnabler, agentClassRegister);

injectMocksUsingAnnotationEnabler(context.getTestInstance(), annotationEnabler);
state.setFrameworkAgentClassRegister();

context.getStore(NAMESPACE).put(KEY, state);
}

@Override
public void afterEach(ExtensionContext context) throws Exception {
// Clear the mock repository after each test
MockRepository.clear();
State state = context.getStore(NAMESPACE).get(KEY, State.class);
clearMockFields(context.getTestInstance(), state.annotationEnabler);
state.clearFrameworkAgentClassRegister();
}

private Object loadAnnotationEnableIfPresent() {
boolean hasAnnotationEnabler = hasAnnotationEnablerClass();
if (!hasAnnotationEnabler) {
return null;
}

try {
return Whitebox.invokeConstructor(Class.forName(ANNOTATION_ENABLER, true, Thread.currentThread().getContextClassLoader()));
} catch (Exception e) {
throw new RuntimeException("PowerMock internal error, failed to load annotation enabler.");
}
}

private boolean hasAnnotationEnablerClass() {
try {
Class.forName(ANNOTATION_ENABLER, false, Thread.currentThread().getContextClassLoader());
return true;
} catch (ClassNotFoundException e) {
return false;
}
}

private void clearMockFields(Object target, Object annotationEnabler) throws Exception {
if (annotationEnabler != null) {
Class<? extends Annotation>[] mockAnnotations = Whitebox.invokeMethod(annotationEnabler, "getMockAnnotations");
Set<Field> mockFields = Whitebox.getFieldsAnnotatedWith(target, mockAnnotations);
for (Field field : mockFields) {
field.set(target, null);
}
}
}

private void injectMocksUsingAnnotationEnabler(Object target, Object annotationEnabler) throws Exception {
if (annotationEnabler != null) {
Whitebox.invokeMethod(annotationEnabler, "beforeTestMethod", new Class<?>[]{Object.class, Method.class,
Object[].class}, target, null, null);
}
}

private static class State {

private final Object annotationEnabler;
private final JavaAgentClassRegister agentClassRegister;
private final JavaAgentFrameworkRegister javaAgentFrameworkRegister;

public State(Object annotationEnabler, JavaAgentClassRegister agentClassRegister) {
this.annotationEnabler = annotationEnabler;
this.agentClassRegister = agentClassRegister;
this.javaAgentFrameworkRegister = JavaAgentFrameworkRegisterFactory.create();
}

private void clearFrameworkAgentClassRegister() {
agentClassRegister.clear();
javaAgentFrameworkRegister.clear();
}

private void setFrameworkAgentClassRegister() {
javaAgentFrameworkRegister.set(agentClassRegister);
}
}
}
2 changes: 2 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ include("powermock-modules:powermock-module-junit4")
include("powermock-modules:powermock-module-junit4-legacy")
include("powermock-modules:powermock-module-junit4-rule")
include("powermock-modules:powermock-module-junit4-rule-agent")
include("powermock-modules:powermock-module-junit-jupiter-agent")
include("powermock-modules:powermock-module-testng-common")
include("powermock-modules:powermock-module-testng")
include("powermock-modules:powermock-module-testng-agent")
Expand All @@ -35,6 +36,7 @@ include("tests:mockito:junit4-agent")
include("tests:mockito:junit4-delegate")
include("tests:mockito:junit4-rule-objenesis")
include("tests:mockito:junit4-rule-xstream")
include("tests:mockito:junit-jupiter-agent")
include("tests:mockito:testng")
include("tests:java8:mockito-junit4")
include("tests:java8:mockito-junit4-agent")
Expand Down
17 changes: 17 additions & 0 deletions tests/mockito/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@ project(":tests:mockito:junit4-delegate") {
}
}

project(":tests:mockito:junit-jupiter-agent") {
description = "Tests for PowerMock JUnit Jupiter extension with Java agent and Mockito."

targetCompatibility = 1.8
sourceCompatibility = 1.8

dependencies {
testCompile project(":powermock-modules:powermock-module-junit-jupiter-agent")

testCompile("org.junit.jupiter:junit-jupiter:${junitJupiterVersion}")
}

test {
useJUnitPlatform()
}
}

project(":tests:mockito:testng"){
description = "Tests for Mockito module with TestNG."

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package samples.powermockito.junit.jupiter.agent;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit.jupiter.PowerMockExtension;
import samples.singleton.SimpleStaticService;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

/**
* Test class to demonstrate static mocking with PowerMockito.
*/
@PrepareForTest(SimpleStaticService.class)
@ExtendWith(PowerMockExtension.class)
class MockStaticTest {

@Test
void testMockStatic() {
String expected = "Hello altered World";
mockStatic(SimpleStaticService.class);
when(SimpleStaticService.say("hello")).thenReturn(expected);

String actual = SimpleStaticService.say("hello");

assertEquals(expected, actual);
}
}