All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lithium.classloadertest.spring.UnfinalizingSpringTestRunner Maven / Gradle / Ivy

The newest version!
package lithium.classloadertest.spring;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

import lithium.classloadertest.FunctionalTestClassLoader;
import lithium.classloadertest.UnfinalizingTestRunner;
import lithium.classloadertest.common.TransformingURLClassLoader;

import org.junit.runners.model.InitializationError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.TestContextManager;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * UnfinalizingSpringTestRunner which provides functionality of the
 * Spring TestContext Framework to standard JUnit 4.5+ tests by means
 * of the {@link TestContextManager} and associated support classes and annotations 
 * see {@link SpringJUnit4ClassRunner} and 
 * at the same time runs it in a different classloader providing unfinalized features
 * see {@link UnfinalizingTestRunner}.  
 * 
 * @author eddie.lo
 */
public class UnfinalizingSpringTestRunner extends UnfinalizingTestRunner {

	private static final Logger logger = LoggerFactory.getLogger(UnfinalizingSpringTestRunner.class);

	/**
	 * Constructs a new SpringJUnit4ClassRunner and initializes a
	 * {@link TestContextManager} to provide Spring testing functionality to
	 * standard JUnit tests.
	 * @param clazz the test class to be run
	 * @throws ClassNotFoundException 
	 */
	public UnfinalizingSpringTestRunner(Class clazz) throws InitializationError, ClassNotFoundException {
		super(clazz);
		if (logger.isDebugEnabled()) {
			logger.debug("UnfinalizingSpringTestRunner constructor called with [" + clazz + "].");
		}
	}

	/**
	 * Delegates to the parent implementation for creating the test instance and
	 * prepare the test instance before returning it.
	 * @see TestContextManager#prepareTestInstance(Object)
	 */
	@Override
	protected Object createTest() throws Exception {
			Object testInstance = super.createTest();
			ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader();
			try {
				TransformingURLClassLoader classLoader = getClassLoader();
				Thread.currentThread().setContextClassLoader(classLoader);
				
				Class loadedClass = classLoader.loadClass(UnfinalizingSpringTestRunner.class.getName());
                Object[] args =  new Object[] {testInstance};
				Method method = getMethodForArgs(loadedClass, "createTestContextManager", args);
				method.invoke(null, args);
				
				return testInstance;
			} catch (Exception e) {
				throw new RuntimeException("Failed to instantiate test class", e);
			} finally {
				if (origClassLoader != null) {
					Thread.currentThread().setContextClassLoader(origClassLoader);
				}
			}
	}

	/**
	 * Copied from  {@link FunctionalTestClassLoader}, should be refactor out later
	 */
	private HashMap>> methodCache = new HashMap>>();

	private List getMethods(Class clazz, String methodName) {
		HashMap> classMethods = methodCache.get(clazz.getName());
		if (classMethods == null) {
			classMethods = new HashMap>();
			methodCache.put(clazz.getName(), classMethods);
			Method methods[] = clazz.getMethods();
			for (Method method : methods) {
				List methodsByName = classMethods.get(method.getName());
				if (methodsByName == null) {
					methodsByName = new ArrayList();
					methodsByName.add(method);
					classMethods.put(method.getName(), methodsByName);
				}
			}
		}
		return classMethods.get(methodName);
	}

	private Method getMethodForArgs(Class loadedClass, String methodName, Object[] args) throws SecurityException,
		NoSuchMethodException {
			List methods = getMethods(loadedClass, methodName);
			if (methods != null) {
				for (Method method : methods) {
					Class paramTypes[] = method.getParameterTypes();
					if (paramTypes.length == args.length) {
						boolean isCompatible = true;
						for (int i = 0; i < paramTypes.length; i++) {
							Class paramType = paramTypes[i];
							Class argType = args[i].getClass();
							if (!paramType.isAssignableFrom(argType)) {
								isCompatible = false;
								break;
							}
						}
						if (isCompatible) {
							return method;
						}
					}
				}
			}
			throw new RuntimeException("Could not find a method named " + methodName + " matching the arguments " + Arrays.toString(args));
	}

	
	/**
	 * Called reflectively by {@link #createTest()} to set up the Spring test context manager in the child
	 * classloader.
	 * 
	 * @param testInstance - an instance of the class under test in the current ClassLoader
	 * @return a new TestContextManager
	 */
	public static TestContextManager createTestContextManager(Object testInstance) {
		TestContextManager testContextManager = new TestContextManager(testInstance.getClass());
		try {
			testContextManager.prepareTestInstance(testInstance);
		} catch (Exception e) {
			throw new RuntimeException("Failed setting up Spring test instance", e);
		}
		return testContextManager;
	}

}