/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.test;
import java.lang.reflect.Method;
import org.jmock.Mockery;
import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestNGListener;
import org.testng.ITestResult;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
/**
* This class can either be used as a base class for tests using JMock
* or it also can be used as a TestNG listener to provide the JMock
* context to test classes that need to inherit from another class.
* <p>
* In the former case, the JMock context is accessible through the {@link #context} protected
* field, while in the latter case (when JMockTest is specified as a {@link Listeners listener}
* of a test class), the JMock context is accessible using the {@link #getCurrentMockContext()}
* static method.
* <p>
* Unlike the default behavior of the Listeners annotation which makes the supplied classes
* the listeners on <b>ALL</b> test methods in <b>ALL</b> classes, this implementation behaves differntly.
* It checks whether the Listeners annotation is specified on the class that the current test
* method is being executed on (or its superclasses) and only if it does, the test method is
* "augmented". This means that the classes can specify if they want to be augmented by JMockTest
* by specifying it as their listener.
*
* @author John Sanda
* @author Lukas Krejci
*/
public class JMockTest implements IHookable, IInvokedMethodListener {
protected Mockery context;
private static final ThreadLocal<Mockery> STATICALLY_ACCESSIBLE_CONTEXT = new ThreadLocal<Mockery>();
/**
* @return the JMock context of the current test or null if the calling test class doesn't have
* this class set as a listener or doesn't inherit from this class.
*/
public static Mockery getCurrentMockContext() {
return STATICALLY_ACCESSIBLE_CONTEXT.get();
}
@BeforeMethod
public final void initMockContext(Method testMethod) {
initBeforeTest(this, testMethod);
}
@AfterMethod
public final void tearDownMockContext(ITestResult testResult) {
tearDownAfterTest(testResult);
}
/**
* This method runs {@link #initBeforeTest(ITestResult)}, followed by the actual test,
* followed by {@link #tearDownAfterTest(ITestResult)}.
* <p>
* If you want to modify the behavior of this method, override the above mentioned
* methods.
*
* @see IHookable#run(IHookCallBack, ITestResult)
*/
public final void run(IHookCallBack iHookCallBack, ITestResult iTestResult) {
iHookCallBack.runTestMethod(iTestResult);
}
/**
* Runs {@link #initBeforeTest(ITestResult)}.
*
* @see IInvokedMethodListener#beforeInvocation(IInvokedMethod, ITestResult)
*/
public final void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
if (!isUsedAsSubClass(method) && isListenerDefinedOnTestClass(method)) {
initBeforeTest(testResult.getInstance(), testResult.getMethod().getMethod());
}
}
/**
* Runs {@link #tearDownAfterTest(ITestResult)}.
*
* @see IInvokedMethodListener#afterInvocation(IInvokedMethod, ITestResult)
*/
public final void afterInvocation(IInvokedMethod method, ITestResult testResult) {
if (!isUsedAsSubClass(method) && isListenerDefinedOnTestClass(method)) {
tearDownAfterTest(testResult);
}
}
/**
* Does whatever needs done before the test is invoked.
* <p>
* If you override this method, be sure to call this method <b>before</b>
* your code so that you gain access to the {@link #context}.
*
* @param testResult
*/
protected void initBeforeTest(Object testObject, Method testMethod) {
initContext();
}
/**
* Does whatever needs done after the test has been invoked.
* <p>
* This method calls {@link Mockery#assertIsSatisfied()} and nulls out the {@link #context}.
* <p>
* If you override this method, call this implmentation as the last call in your code.
* @param result
*/
protected void tearDownAfterTest(ITestResult result) {
try {
context.assertIsSatisfied();
} catch (Throwable t) {
result.setStatus(ITestResult.FAILURE);
result.setThrowable(t);
} finally {
tearDownContext();
}
}
private void initContext() {
context = new Mockery();
STATICALLY_ACCESSIBLE_CONTEXT.set(context);
}
private void tearDownContext() {
context = null;
//null out the static field so that the GC can
//collect the no-longer used context.
STATICALLY_ACCESSIBLE_CONTEXT.set(null);
}
private boolean isUsedAsSubClass(IInvokedMethod method) {
Class<?> testMethodClass = method.getTestMethod().getTestClass().getRealClass();
return this.getClass().isAssignableFrom(testMethodClass);
}
private boolean isListenerDefinedOnTestClass(IInvokedMethod method) {
Class<?> cls = method.getTestMethod().getTestClass().getRealClass();
while (cls != null) {
Listeners annotation = cls.getAnnotation(Listeners.class);
if (annotation != null) {
for(Class<?> listener : annotation.value()) {
if (this.getClass().equals(listener)) {
return true;
}
}
}
cls = cls.getSuperclass();
}
return false;
}
}