// Copyright � 2002-2007 Canoo Engineering AG, Switzerland.
package com.canoo.webtest.steps;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import junit.framework.AssertionFailedError;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.RuntimeConfigurable;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.UnknownElement;
import com.agical.rmock.extension.junit.RMockTestCase;
import com.canoo.webtest.ant.WebtestPropertyHelper;
import com.canoo.webtest.ant.WebtestTask;
import com.canoo.webtest.boundary.HtmlUnitBoundary;
import com.canoo.webtest.boundary.UrlBoundary;
import com.canoo.webtest.engine.StepExecutionException;
import com.canoo.webtest.engine.StepFailedException;
import com.canoo.webtest.reporting.IResultReporter;
import com.canoo.webtest.reporting.RootStepResult;
import com.canoo.webtest.self.ContextStub;
import com.canoo.webtest.self.TestBlock;
import com.canoo.webtest.self.ThrowAssert;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
/**
* Tests that are common for all Steps, especially parameter handling.
*
* @author Marc Guillemot
* @author Denis N. Antonioli
* @author Paul King
*/
public abstract class BaseStepTestCase extends RMockTestCase {
protected static final String NO_CURRENT_RESPONSE = "No current response available! Is previous invoke missing?";
private static final String NO_CURRENT_RESPONSEFILE = "No current response file available!";
private ContextStub fContext;
private Step fStep;
private Project fProject;
private Target fTarget;
/**
* The minimal string to have the verification of toString pass.
* Useful for simplistic implmentation of abstract class.
*/
public static final String MOCK_TO_STRING = ")";
/**
* Creates the context and the Step under test calling {@link #createStep()}.
*
* @see junit.framework.TestCase#setUp()
*/
protected void setUp() throws Exception {
super.setUp();
fProject = new Project();
fProject.init();
WebtestPropertyHelper.configureWebtestPropertyHelper(fProject);
fContext = createContext();
fTarget = new Target();
fTarget.setProject(fProject);
fStep = createAndConfigureStep();
}
protected void tearDown() throws Exception {
super.tearDown();
// Junit releases test instances only at the end, we need to release resources earlier
fProject = null;
fContext = null;
fTarget = null;
fStep = null;
}
protected ContextStub getContext() {
return fContext;
}
/**
* Creates the context that will be created in {@link #setUp()}, available through {@link #getContext()}
* and set on the step created with {@link #createStep()}
*
* @return the context
*/
protected ContextStub createContext() {
final ContextStub cx = new ContextStub();
cx.getWebtest().setProject(getProject());
return cx;
}
protected void setFakedContext(final ContextStub context) throws IOException
{
if (fContext.getCurrentResponse() != null)
fContext.getCurrentResponse().cleanUp();
fContext = context;
WebtestTask.setThreadContext(context);
}
/**
* Gets a html page with a very basic content
*
* @return the page
*/
protected HtmlPage getDummyPage() {
return getDummyPage("<html></html>");
}
/**
* Gets a html page with the given content
*/
protected HtmlPage getDummyPage(final String content) {
return (HtmlPage) getDummyPage(content, "text/html");
}
/**
* Gets a page with the given content and content type
*/
protected Page getDummyPage(final String content, final String contentType) {
fContext.setDefaultResponse(content, contentType);
final URL url = UrlBoundary.tryCreateUrl("http://webtest.canoo.com");
return HtmlUnitBoundary.tryGetPage(url, getContext().getWebClient());
}
public void testUnknownPropertyType() {
final Step step = getStep();
try {
step.getWebtestProperty("antProp", "unknown");
///CLOVER:OFF will not occur if we write steps correctly
fail("Should raise a StepExecutionException");
///CLOVER:ON
} catch (StepExecutionException expected) {
assertTrue(true);
}
}
public void testToString() {
final Step step = getStep();
final String result = step.toString();
assertNotNull(result);
assertTrue("toString() should end with ')' but found: " + result, result.endsWith(")"));
}
/**
* Gets the step beeing tested. This should not be called during class initialisation because
* a new instance is created before each test execution.
*
* @return the currently tested step created during {@link #setUp()} by {@link #createStep()}.
*/
protected final Step getStep() {
return fStep;
}
/**
* Concrete test classes should return the Step they want to test.
* The step is created during {@link #setUp()} and the context is set on it to {@link #getContext()}
*
* @return the tested step.
*/
protected abstract Step createStep();
/**
* Creates a new step using {@link #createStep()} and sets the current context on it
*
* @return the step
*/
protected final Step createAndConfigureStep() {
return configureStep(createStep());
}
/**
* Configures the step a la webtest, setting the properties ant/webtest would have set on it before executing
* it (Project, Context, ...).
*
* @param step the step to configure
* @return the configured step
*/
protected Step configureStep(final Step step) {
configureTask(step);
return step;
}
/**
* Configures the task a la ant, setting the properties ant would have set on it before executing
* it (Project, Target, ...).
*
* @param task the task to configure
* @return the configured task
*/
protected Task configureTask(final Task task) {
task.setProject(fProject);
task.setOwningTarget(fTarget);
return task;
}
/**
* Gets the project used for the current test
* @return the project
*/
protected Project getProject()
{
return fProject;
}
public static void assertStepRejectsNullResponse(final Step step) {
((ContextStub) step.getContext()).fakeLastResponse(null);
assertErrorOnExecute(step, "currentResponse == null", NO_CURRENT_RESPONSE);
}
protected void assertStepRejectsNullResponseFile(final Step step) throws Exception {
getContext().fakeLastResponse(null);
assertErrorOnExecute(step, "currentResponseFile == null", NO_CURRENT_RESPONSEFILE);
}
protected static void checkResponseMessage(final String expectedMessage, final String message) {
///CLOVER:OFF will not occur if we write steps correctly
final String actualMessage = null == message ? "null" : message;
if (!actualMessage.startsWith(expectedMessage)) {
fail("expected start <" + expectedMessage + "> but was <" + actualMessage + ">");
}
///CLOVER:ON
}
protected static void assertStepRejectsNullParam(final String param, final TestBlock b) {
final Throwable t = ThrowAssert.assertThrows("param == null", StepExecutionException.class, b);
assertEquals("Required parameter \"" + param + "\" not set!", t.getMessage());
}
public static void assertStepRejectsEmptyParam(final String param, final TestBlock b) {
final Throwable t = ThrowAssert.assertThrows("param == null or zero length", StepExecutionException.class, b);
assertEquals("Required parameter \"" + param + "\" not set or set to empty string!", t.getMessage());
}
/**
* Gets a test block calling {@link #executeStep(Step)} with the provided step.
* @return the test block
*/
protected static TestBlock getExecuteStepTestBlock(final Step step)
{
return new TestBlock() {
public void call() throws Exception {
executeStep(step);
}
};
}
/**
* Gets a test block calling {@link #executeStep(Step)} on {@link #getStep()}.
* @return the test block
*/
protected TestBlock getExecuteStepTestBlock()
{
return getExecuteStepTestBlock(getStep());
}
/**
* @return the thrown exception
*/
protected static Throwable assertThrowOnExecute(final Step step, final String failMessage, final String exceptionMessagePrefix,
final Class<?> throwable) {
final Throwable t = ThrowAssert.assertThrows(failMessage, throwable, getExecuteStepTestBlock(step));
final String message = t.getMessage();
if (!message.startsWith(exceptionMessagePrefix)) {
fail("expected start <" + exceptionMessagePrefix + "> but was <" + message + ">");
}
return t;
}
/**
* @param failMessage
* @param exceptionMessagePrefix
* @return the thrown exception
*/
protected static Throwable assertFailOnExecute(final Step step, final String failMessage, final String exceptionMessagePrefix) {
return assertThrowOnExecute(step, failMessage, exceptionMessagePrefix, StepFailedException.class);
}
/**
* @return the thrown exception
*/
protected Throwable assertFailOnExecute(final Step step) {
return assertThrowOnExecute(step, "", "", StepFailedException.class);
}
/**
* @return the thrown exception
*/
public static Throwable assertErrorOnExecute(final Step step, final String failMessage, final String exceptionMessagePrefix) {
return assertThrowOnExecute(step, failMessage, exceptionMessagePrefix, StepExecutionException.class);
}
/**
* @return the thrown exception
*/
protected Throwable assertErrorOnExecute(final Step step) {
return assertThrowOnExecute(step, "", "", StepExecutionException.class);
}
protected void assertErrorOnExecuteIfCurrentPageIsXml(final Step step) {
step.getContext().saveResponseAsCurrent(getDummyPage("<xml></xml>", "text/xml"));
ThrowAssert.assertThrows(StepExecutionException.class,
"Current response is not an HTML page", new TestBlock() {
public void call() throws Exception {
executeStep(step);
}
});
}
public static void assertInstanceOf(final Class expected, final Object actual) {
if (!expected.isInstance(actual)) {
fail("expected: <" + expected.getName() + "> but was <" + actual.getClass().getName() + ">.");
}
}
public static void testAssertInstanceOf() {
assertInstanceOf(Boolean.class, Boolean.TRUE);
ThrowAssert.assertThrows(AssertionFailedError.class, new TestBlock() {
public void call() throws Throwable {
assertInstanceOf(Integer.class, Boolean.TRUE);
}
});
}
/**
* Use this method to execute a step.
* The method duplicates the content of {@link Step#execute()}, but without error
* handling and without notification. It assumes the context is already set.
*
* @param step The step to execute.
* @throws Exception
*/
public static void executeStep(final Step step) throws Exception {
step.verifyParameters();
step.doExecute();
}
public static ContextStub getContextForDocument(final String documentText) {
return new ContextStub(documentText);
}
/**
* Test that calling addText(String) on the step sets the specified property.
* @param step the step
* @param propertyName the property that addText is expected to fill
* @throws Exception
*/
public static void testNestedTextEquivalent(final Step step, final String propertyName) throws Exception {
final String textToSet = "blabla";
final Method addText = step.getClass().getMethod("addText", String.class);
addText.invoke(step, textToSet);
final Method getter = step.getClass().getMethod("get" + StringUtils.capitalize(propertyName));
final String value = (String) getter.invoke(step);
assertEquals("test value of property " + propertyName, textToSet, value);
}
protected RuntimeConfigurable parseStep(final Class aClass, final String attributes) {
return parseStep(getProject(), aClass, attributes);
}
/**
*
* @param aClass the class of the step to create
* @param attributes the attributes
* @return a RuntimeConfigurable as would Ant do it while parsing
*/
public static RuntimeConfigurable parseStep(final Project project, final Class aClass, final String attributes) {
final String tagName = ClassUtils.getShortClassName(aClass);
final UnknownElement task = new UnknownElement(tagName);
task.setQName(tagName);
task.setTaskType(tagName);
task.setTaskName(tagName);
task.setProject(project);
project.addTaskDefinition(tagName, aClass);
final RuntimeConfigurable wrapper = new RuntimeConfigurable(task, task.getTaskName());
final Pattern p = Pattern.compile("(\\w+)='([^']*)'");
final Matcher m = p.matcher(attributes);
while (m.find()) {
final String name = m.group(1);
final String value = m.group(2);
wrapper.setAttribute(name, value);
}
return wrapper;
}
public static File getTemporaryResultPathFolder() throws IOException {
final File tmpFolder = new File(System.getProperty("java.io.tmpdir"));
File tmpWebTestFolder = new File(tmpFolder, "webtest-unittest-tmp");
tmpWebTestFolder.deleteOnExit();
FileUtils.forceMkdir(tmpWebTestFolder);
assertTrue(tmpFolder.isDirectory());
return tmpWebTestFolder;
}
public static void registerDummyResultReporter(final Project project) {
project.setProperty(WebtestTask.REPORTER_CLASSNAME_PROPERTY, DummyResultReporter.class.getName());
}
/**
* An implementation of {@link IResultReporter} doing nothing just to avoid error with the default result reporter.
*/
public static class DummyResultReporter implements IResultReporter {
@Override
public void generateReport(final RootStepResult result) throws Exception {
// nothing
}
}
}