Support running test methods on the server side (#44937)

Closes #44936

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen 2025-12-17 13:15:42 +01:00 committed by GitHub
parent 9597537bf3
commit aa6890f539
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 271 additions and 71 deletions

View File

@ -1,8 +1,8 @@
package org.keycloak.testframework;
import java.lang.reflect.Method;
import java.util.Optional;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.injection.Registry;
import org.junit.jupiter.api.extension.AfterAllCallback;
@ -10,82 +10,82 @@ import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.InvocationInterceptor;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
import org.junit.jupiter.api.extension.TestWatcher;
public class KeycloakIntegrationTestExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback, TestWatcher {
public class KeycloakIntegrationTestExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback, TestWatcher, InvocationInterceptor, ParameterResolver {
private static final LogHandler logHandler = new LogHandler();
@Override
public void beforeAll(ExtensionContext context) {
if (isExtensionEnabled(context)) {
logHandler.beforeAll(context);
}
logHandler.beforeAll(context);
}
@Override
public void beforeEach(ExtensionContext context) {
if (isExtensionEnabled(context)) {
logHandler.beforeEachStarting(context);
getRegistry(context).beforeEach(context.getRequiredTestInstance());
logHandler.beforeEachCompleted(context);
}
logHandler.beforeEachStarting(context);
getRegistry(context).beforeEach(context.getRequiredTestInstance(), context.getRequiredTestMethod());
logHandler.beforeEachCompleted(context);
}
@Override
public void afterEach(ExtensionContext context) {
if (isExtensionEnabled(context)) {
logHandler.afterEachStarting(context);
getRegistry(context).afterEach();
logHandler.afterEachCompleted(context);
}
logHandler.afterEachStarting(context);
getRegistry(context).afterEach();
logHandler.afterEachCompleted(context);
}
@Override
public void afterAll(ExtensionContext context) {
if (isExtensionEnabled(context)) {
logHandler.afterAll(context);
getRegistry(context).afterAll();
}
logHandler.afterAll(context);
getRegistry(context).afterAll();
}
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
if (isExtensionEnabled(context)) {
logHandler.testFailed(context);
}
logHandler.testFailed(context);
}
@Override
public void testDisabled(ExtensionContext context, Optional<String> reason) {
if (isExtensionEnabled(context)) {
logHandler.testDisabled(context);
}
logHandler.testDisabled(context);
}
@Override
public void testSuccessful(ExtensionContext context) {
if (isExtensionEnabled(context)) {
logHandler.testSuccessful(context);
}
logHandler.testSuccessful(context);
}
@Override
public void testAborted(ExtensionContext context, Throwable cause) {
if (isExtensionEnabled(context)) {
logHandler.testAborted(context);
}
logHandler.testAborted(context);
}
private boolean isExtensionEnabled(ExtensionContext context) {
return context.getRequiredTestClass().isAnnotationPresent(KeycloakIntegrationTest.class);
@Override
public void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
getRegistry(extensionContext).intercept(invocation, invocationContext);
}
private Registry getRegistry(ExtensionContext context) {
public static Registry getRegistry(ExtensionContext context) {
ExtensionContext.Store store = context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL);
Registry registry = (Registry) store.getOrComputeIfAbsent(Registry.class, r -> new Registry());
registry.setCurrentContext(context);
return registry;
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext context) throws ParameterResolutionException {
return getRegistry(context).supportsParameter(parameterContext, context);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context) throws ParameterResolutionException {
// As this is only used by custom test executors for now they are responsible for injecting the parameter, hence returning null here
return null;
}
}

View File

@ -0,0 +1,18 @@
package org.keycloak.testframework;
import java.lang.reflect.Method;
import java.util.List;
import org.keycloak.testframework.injection.Registry;
public interface TestFrameworkExecutor {
List<Class<?>> getMethodValueTypes(Method method);
boolean supportsParameter(Method method, Class<?> parameterType);
boolean shouldExecute(Method testMethod);
void execute(Registry registry, Class<?> testClass, Method testMethod);
}

View File

@ -5,11 +5,15 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.keycloak.testframework.KeycloakIntegrationTestExtension;
import org.keycloak.testframework.server.DefaultKeycloakServerConfig;
import org.keycloak.testframework.server.KeycloakServerConfig;
import org.junit.jupiter.api.extension.ExtendWith;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ExtendWith({KeycloakIntegrationTestExtension.class})
public @interface KeycloakIntegrationTest {
Class<? extends KeycloakServerConfig> config() default DefaultKeycloakServerConfig.class;

View File

@ -2,6 +2,7 @@ package org.keycloak.testframework.injection;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
@ -9,6 +10,7 @@ import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import org.keycloak.testframework.TestFrameworkExecutor;
import org.keycloak.testframework.TestFrameworkExtension;
import org.keycloak.testframework.config.Config;
@ -22,6 +24,7 @@ public class Extensions {
private final List<Class<?>> alwaysEnabledValueTypes;
private static Extensions INSTANCE;
private final List<TestFrameworkExtension> extensions;
public static Extensions getInstance() {
if (INSTANCE == null) {
@ -35,7 +38,7 @@ public class Extensions {
}
private Extensions() {
List<TestFrameworkExtension> extensions = loadExtensions();
extensions = loadExtensions();
valueTypeAlias = loadValueTypeAlias(extensions);
Config.registerValueTypeAlias(valueTypeAlias);
logger = new RegistryLogger(valueTypeAlias);
@ -55,6 +58,19 @@ public class Extensions {
return alwaysEnabledValueTypes;
}
public List<TestFrameworkExecutor> getTestFrameworkExecutors() {
return extensions.stream()
.filter(e -> e instanceof TestFrameworkExecutor)
.map(e -> (TestFrameworkExecutor) e)
.toList();
}
public List<Class<?>> getMethodValueTypes(Method method) {
return getTestFrameworkExecutors()
.stream()
.flatMap(e -> e.getMethodValueTypes(method).stream()).toList();
}
@SuppressWarnings("unchecked")
public <T> Supplier<T, ?> findSupplierByType(Class<T> typeClass) {
return (Supplier<T, ?>) suppliers.stream().filter(s -> s.getValueType().equals(typeClass)).findFirst().orElse(null);

View File

@ -2,6 +2,7 @@ package org.keycloak.testframework.injection;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
@ -9,7 +10,12 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.keycloak.testframework.TestFrameworkExecutor;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.InvocationInterceptor;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
@SuppressWarnings({"rawtypes", "unchecked"})
public class Registry implements ExtensionContext.Store.CloseableResource {
@ -112,8 +118,8 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
return (T) dependency.getValue();
}
public void beforeEach(Object testInstance) {
findRequestedInstances(testInstance);
public void beforeEach(Object testInstance, Method testMethod) {
findRequestedInstances(testInstance, testMethod);
destroyIncompatibleInstances();
matchDeployedInstancesWithRequestedInstances();
deployRequestedInstances();
@ -121,7 +127,27 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
invokeBeforeEachOnSuppliers();
}
private void findRequestedInstances(Object testInstance) {
public void intercept(InvocationInterceptor.Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext) throws Throwable {
Class<?> testClass = invocationContext.getTargetClass();
Method testMethod = invocationContext.getExecutable();
TestFrameworkExecutor testFrameworkExecutor = getExecutor(testMethod);
if (testFrameworkExecutor != null) {
testFrameworkExecutor.execute(this, testClass, testMethod);
invocation.skip();
} else {
invocation.proceed();
}
}
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
Method testMethod = (Method) parameterContext.getParameter().getDeclaringExecutable();
Class<?> parameterType = parameterContext.getParameter().getType();
TestFrameworkExecutor testFrameworkExecutor = getExecutor(testMethod);
return testFrameworkExecutor != null && testFrameworkExecutor.supportsParameter(testMethod, parameterType);
}
private void findRequestedInstances(Object testInstance, Method testMethod) {
List<Class<?>> alwaysEnabledValueTypes = extensions.getAlwaysEnabledValueTypes();
for (Class<?> valueType : alwaysEnabledValueTypes) {
RequestedInstance requestedInstance = createRequestedInstance(null, valueType);
@ -130,6 +156,14 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
}
}
List<Class<?>> methodValueTypes = extensions.getMethodValueTypes(testMethod);
for (Class<?> valueType : methodValueTypes) {
RequestedInstance requestedInstance = createRequestedInstance(null, valueType);
if (requestedInstance != null) {
requestedInstances.add(requestedInstance);
}
}
Class testClass = testInstance.getClass();
RequestedInstance requestedServerInstance = createRequestedInstance(testClass.getAnnotations(), null);
if (requestedServerInstance != null) {
@ -317,6 +351,10 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
}
}
private TestFrameworkExecutor getExecutor(Method testMethod) {
return extensions.getTestFrameworkExecutors().stream().filter(e -> e.shouldExecute(testMethod)).findFirst().orElse(null);
}
private static class RequestedInstanceComparator implements Comparator<RequestedInstance> {
static final RequestedInstanceComparator INSTANCE = new RequestedInstanceComparator();

View File

@ -1 +0,0 @@
org.keycloak.testframework.KeycloakIntegrationTestExtension

View File

@ -1,5 +1,6 @@
package org.keycloak.testframework.injection;
import java.lang.reflect.Method;
import java.util.List;
import org.keycloak.testframework.config.Config;
@ -36,7 +37,7 @@ public class RegistryTest {
MockParentSupplier.DEFAULT_LIFECYCLE = LifeCycle.GLOBAL;
ParentTest test = new ParentTest();
registry.beforeEach(test);
runBeforeEach(test);
MockParentValue value1 = test.parent;
assertRunning(value1);
@ -45,7 +46,7 @@ public class RegistryTest {
assertRunning(value1);
ParentTest test2 = new ParentTest();
registry.beforeEach(test2);
runBeforeEach(test2);
MockParentValue value2 = test2.parent;
Assertions.assertSame(value1, value2);
@ -59,14 +60,14 @@ public class RegistryTest {
public void testClassLifeCycle() {
ParentTest test = new ParentTest();
registry.beforeEach(test);
runBeforeEach(test);
MockParentValue value1 = test.parent;
assertRunning(value1);
registry.afterEach();
assertRunning(value1);
registry.beforeEach(test);
runBeforeEach(test);
MockParentValue value2 = test.parent;
Assertions.assertSame(value1, value2);
@ -77,7 +78,7 @@ public class RegistryTest {
assertClosed(value1);
ParentTest test2 = new ParentTest();
registry.beforeEach(test2);
runBeforeEach(test2);
MockParentValue value3 = test2.parent;
Assertions.assertNotSame(value1, value3);
@ -89,14 +90,14 @@ public class RegistryTest {
ParentTest test = new ParentTest();
registry.beforeEach(test);
runBeforeEach(test);
MockParentValue value1 = test.parent;
assertRunning(value1);
registry.afterEach();
assertClosed(value1);
registry.beforeEach(test);
runBeforeEach(test);
MockParentValue value2 = test.parent;
Assertions.assertNotSame(value1, value2);
}
@ -109,14 +110,14 @@ public class RegistryTest {
ParentAndChildTest parentAndChildTest = new ParentAndChildTest();
MockChildValue child1 = parentAndChildTest.child;
registry.beforeEach(parentAndChildTest);
runBeforeEach(parentAndChildTest);
assertRunning(parentAndChildTest.parent, parentAndChildTest.child);
Assertions.assertSame(parentAndChildTest.parent, parentAndChildTest.child.getParent());
registry.afterEach();
assertClosed(parentAndChildTest.parent, parentAndChildTest.child);
registry.beforeEach(parentAndChildTest);
runBeforeEach(parentAndChildTest);
Assertions.assertNotSame(child1, parentAndChildTest.child);
}
@ -126,7 +127,7 @@ public class RegistryTest {
ParentTest parentTest = new ParentTest();
registry.beforeEach(parentTest);
runBeforeEach(parentTest);
registry.afterEach();
registry.afterAll();
assertRunning(parentTest.parent);
@ -134,7 +135,7 @@ public class RegistryTest {
MockParentSupplier.DEFAULT_LIFECYCLE = LifeCycle.CLASS;
ParentTest parentTest2 = new ParentTest();
registry.beforeEach(parentTest2);
runBeforeEach(parentTest2);
assertRunning(parentTest2.parent);
assertClosed(parentTest.parent);
@ -145,13 +146,13 @@ public class RegistryTest {
public void testRecreateIfNotCompatible() {
ParentTest parentTest = new ParentTest();
registry.beforeEach(parentTest);
runBeforeEach(parentTest);
MockParentValue parent1 = parentTest.parent;
registry.afterEach();
MockParentSupplier.COMPATIBLE = false;
registry.beforeEach(parentTest);
runBeforeEach(parentTest);
registry.afterEach();
MockParentValue parent2 = parentTest.parent;
@ -191,7 +192,7 @@ public class RegistryTest {
public void testDependencyCreatedOnDemand() {
ChildTest childTest = new ChildTest();
registry.beforeEach(childTest);
runBeforeEach(childTest);
assertRunning(childTest.child, childTest.child.getParent());
}
@ -199,7 +200,7 @@ public class RegistryTest {
public void testDependencyRequestedBefore() {
ParentAndChildTest test = new ParentAndChildTest();
registry.beforeEach(test);
runBeforeEach(test);
assertRunning(test.child, test.child.getParent());
}
@ -207,14 +208,14 @@ public class RegistryTest {
public void testDependencyRequestedAfter() {
ChildAndParentTest test = new ChildAndParentTest();
registry.beforeEach(test);
runBeforeEach(test);
assertRunning(test.child, test.child.getParent());
}
@Test
public void testMultiplRef() {
MultipleRefTest refTest = new MultipleRefTest();
registry.beforeEach(refTest);
runBeforeEach(refTest);
MockParentValue def1 = refTest.def;
MockParentValue a1 = refTest.a;
@ -228,7 +229,7 @@ public class RegistryTest {
registry.afterEach();
registry.beforeEach(refTest);
runBeforeEach(refTest);
assertRunning(refTest.def, refTest.a2, refTest.b);
Assertions.assertSame(def1, refTest.def);
@ -244,7 +245,7 @@ public class RegistryTest {
@Test
public void testRealmRef() {
RealmRefTest test = new RealmRefTest();
registry.beforeEach(test);
runBeforeEach(test);
assertRunning(test.childABC, test.childABC.getParent(), test.child123, test.parent123);
Assertions.assertNotSame(test.childABC.getParent(), test.parent123);
@ -254,7 +255,7 @@ public class RegistryTest {
@Test
public void testConfigurableSupplier() {
ParentTest parentTest = new ParentTest();
registry.beforeEach(parentTest);
runBeforeEach(parentTest);
Assertions.assertNull(parentTest.parent.getStringOption());
Assertions.assertTrue(parentTest.parent.isBooleanOption());
@ -267,7 +268,7 @@ public class RegistryTest {
Extensions.reset();
registry = new Registry();
parentTest = new ParentTest();
registry.beforeEach(parentTest);
runBeforeEach(parentTest);
Assertions.assertEquals("some string", parentTest.parent.getStringOption());
Assertions.assertFalse(parentTest.parent.isBooleanOption());
@ -282,7 +283,7 @@ public class RegistryTest {
public void testIncompatibleParent() {
MockParentSupplier.COMPATIBLE = false;
RealmIncompatibleParentTest test = new RealmIncompatibleParentTest();
registry.beforeEach(test);
runBeforeEach(test);
MockParentValue parent1 = test.parent;
MockChildValue child1 = test.child;
@ -292,7 +293,7 @@ public class RegistryTest {
registry.afterEach();
registry.beforeEach(test);
runBeforeEach(test);
Assertions.assertNotNull(test.parent);
Assertions.assertNotEquals(parent1, test.parent);
@ -300,6 +301,15 @@ public class RegistryTest {
Assertions.assertNotEquals(child1, test.child);
}
private <T extends AbstractTest> void runBeforeEach(T testInstance) {
try {
Method testMethod = testInstance.getClass().getMethod("test");
registry.beforeEach(testInstance, testMethod);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public static void assertRunning(Object... values) {
MatcherAssert.assertThat(MockInstances.INSTANCES, Matchers.hasItems(values));
MatcherAssert.assertThat(MockInstances.INSTANCES, Matchers.hasSize(values.length));
@ -310,7 +320,13 @@ public class RegistryTest {
MatcherAssert.assertThat(MockInstances.CLOSED_INSTANCES, Matchers.hasSize(values.length));
}
public static final class ParentAndChildTest {
public static abstract class AbstractTest {
@Test
public void test() {
}
}
public static final class ParentAndChildTest extends AbstractTest {
@MockParentAnnotation
MockParentValue parent;
@ -318,17 +334,17 @@ public class RegistryTest {
MockChildValue child;
}
public static final class ParentTest {
public static final class ParentTest extends AbstractTest {
@MockParentAnnotation
MockParentValue parent;
}
public static final class ChildTest {
public static final class ChildTest extends AbstractTest {
@MockChildAnnotation
MockChildValue child;
}
public static final class ChildAndParentTest {
public static final class ChildAndParentTest extends AbstractTest {
@MockChildAnnotation
MockChildValue child;
@ -336,7 +352,7 @@ public class RegistryTest {
MockParentValue parent;
}
public static final class MultipleRefTest {
public static final class MultipleRefTest extends AbstractTest {
@MockParentAnnotation()
MockParentValue def;
@ -350,7 +366,7 @@ public class RegistryTest {
MockParentValue b;
}
public static final class RealmRefTest {
public static final class RealmRefTest extends AbstractTest {
@MockParentAnnotation(ref = "123")
MockParentValue parent123;
@ -361,7 +377,7 @@ public class RegistryTest {
MockChildValue childABC;
}
public static final class RealmIncompatibleParentTest {
public static final class RealmIncompatibleParentTest extends AbstractTest {
@MockChildAnnotation
MockChildValue child;

View File

@ -0,0 +1,29 @@
package org.keycloak.test.examples;
import org.keycloak.models.KeycloakSession;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.remote.annotations.TestOnServer;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@KeycloakIntegrationTest
public class RunTestOnServerTest {
@InjectRealm
ManagedRealm realm;
@TestOnServer
public void test(KeycloakSession session) throws Throwable {
Assertions.assertNotNull(session);
Assertions.assertNull(realm);
}
@Test
public void test2() {
Assertions.assertNotNull(realm);
}
}

View File

@ -1,2 +1 @@
junit.jupiter.extensions.autodetection.enabled=true
junit.jupiter.testclass.order.default=org.keycloak.testframework.ServerConfigClassOrderer

View File

@ -1,14 +1,22 @@
package org.keycloak.testframework.remote;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import org.keycloak.models.KeycloakSession;
import org.keycloak.testframework.TestFrameworkExecutor;
import org.keycloak.testframework.TestFrameworkExtension;
import org.keycloak.testframework.injection.Registry;
import org.keycloak.testframework.injection.Supplier;
import org.keycloak.testframework.remote.annotations.TestOnServer;
import org.keycloak.testframework.remote.runonserver.RunOnServerClient;
import org.keycloak.testframework.remote.runonserver.RunOnServerSupplier;
import org.keycloak.testframework.remote.runonserver.RunTestOnServer;
import org.keycloak.testframework.remote.runonserver.TestClassServerSupplier;
import org.keycloak.testframework.remote.timeoffset.TimeOffsetSupplier;
public class RemoteTestFrameworkExtension implements TestFrameworkExtension {
public class RemoteTestFrameworkExtension implements TestFrameworkExtension, TestFrameworkExecutor {
@Override
public List<Supplier<?, ?>> suppliers() {
return List.of(
@ -23,4 +31,32 @@ public class RemoteTestFrameworkExtension implements TestFrameworkExtension {
public List<Class<?>> alwaysEnabledValueTypes() {
return List.of(RemoteProviders.class);
}
@Override
public List<Class<?>> getMethodValueTypes(Method method) {
return isTestOnServer(method) ? List.of(RunOnServerClient.class) : Collections.emptyList();
}
@Override
public boolean supportsParameter(Method method, Class<?> parameterType) {
return isTestOnServer(method) && parameterType.equals(KeycloakSession.class);
}
@Override
public boolean shouldExecute(Method testMethod) {
return isTestOnServer(testMethod);
}
@Override
public void execute(Registry registry, Class<?> testClass, Method testMethod) {
RunOnServerClient value = (RunOnServerClient) registry.getDeployedInstances().stream().filter(i -> i.getRequestedValueType() != null && i.getRequestedValueType().equals(RunOnServerClient.class)).findFirst().get().getValue();
RunTestOnServer runTestOnServer = new RunTestOnServer(testClass.getName(), testMethod.getName());
value.run(runTestOnServer);
}
private boolean isTestOnServer(Method method) {
return method.isAnnotationPresent(TestOnServer.class);
}
}

View File

@ -0,0 +1,14 @@
package org.keycloak.testframework.remote.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Test;
@Test
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestOnServer {
}

View File

@ -0,0 +1,31 @@
package org.keycloak.testframework.remote.runonserver;
import java.io.IOException;
import java.lang.reflect.Method;
import org.keycloak.common.VerificationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.testframework.remote.providers.runonserver.RunOnServer;
public class RunTestOnServer implements RunOnServer {
private final String testClass;
private final String testMethod;
public RunTestOnServer(String testClass, String testMethod) {
this.testClass = testClass;
this.testMethod = testMethod;
}
@Override
public void run(KeycloakSession session) throws IOException, VerificationException {
try {
Class<?> clazz = this.getClass().getClassLoader().loadClass(testClass);
Object test = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod(testMethod, KeycloakSession.class);
method.invoke(test, session);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}