com.google.gwt.junit.client.GWTTestCase Maven / Gradle / Ivy
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.junit.client;
import com.google.gwt.junit.JUnitShell;
import com.google.gwt.junit.JUnitShell.Strategy;
import com.google.gwt.junit.PropertyDefiningStrategy;
import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
import junit.framework.TestCase;
import junit.framework.TestResult;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* Acts as a bridge between the JUnit environment and the GWT environment. We
* hook the run method and stash the TestResult object for later communication
* between the test runner and the unit test shell that drives the test case
* inside a hosted browser.
*
*
* There are two versions of this class. This version is the binary version that
* derives from JUnit's {@link TestCase} and handles all the work of starting up
* the GWT environment. The other version is a translatable class that is used
* within the browser. See the translatable
subpackage for the
* translatable implementation.
*
*/
@SuppressWarnings("unused")
public abstract class GWTTestCase extends TestCase {
/**
* Information about a synthetic module used for testing.
*/
public static final class TestModuleInfo {
private String moduleName;
private String syntheticModuleName;
private Strategy strategy;
/**
* The ordered tests in this synthetic module.
*/
private Set tests = new LinkedHashSet();
/**
* Construct a new {@link TestModuleInfo}.
*
* @param moduleName the module name
* @param syntheticModuleName the synthetic module name
* @param strategy the test {@link Strategy}
*/
public TestModuleInfo(String moduleName, String syntheticModuleName,
Strategy strategy) {
this.moduleName = moduleName;
this.syntheticModuleName = syntheticModuleName;
this.strategy = strategy;
}
public String getModuleName() {
return moduleName;
}
public Strategy getStrategy() {
return strategy;
}
public String getSyntheticModuleName() {
return syntheticModuleName;
}
/**
* Returns the tests that are part of this module.
*/
public Set getTests() {
return tests;
}
}
/**
* Records all live GWTTestCases by synthetic module name so we can optimize
* run they are compiled and run. Ordered so that we can precompile the
* modules in the order that they will run.
*/
public static final Map ALL_GWT_TESTS = new LinkedHashMap();
/**
* The lock for ALL_GWT_TESTS.
*/
private static final Object ALL_GWT_TESTS_LOCK = new Object();
/**
* Get the names of all test modules.
*
* @return all test module names
*/
public static String[] getAllTestModuleNames() {
synchronized (ALL_GWT_TESTS_LOCK) {
return ALL_GWT_TESTS.keySet().toArray(new String[ALL_GWT_TESTS.size()]);
}
}
/**
* Get the number of modules.
*
* @return the module count.
*/
public static int getModuleCount() {
synchronized (ALL_GWT_TESTS_LOCK) {
return ALL_GWT_TESTS.size();
}
}
/**
* Get the set of all {@link TestInfo} for the specified module.
*
* @param syntheticModuleName the synthetic module name
* @return all tests for the module
*/
public static TestModuleInfo getTestsForModule(String syntheticModuleName) {
synchronized (ALL_GWT_TESTS_LOCK) {
return ALL_GWT_TESTS.get(syntheticModuleName);
}
}
/**
* Object that collects the results of this test case execution.
*/
protected TestResult testResult = null;
/**
* Whether this test case should be always run in pure Java mode (non-GWT).
* Setting this to true
has the same effect as returning
* null
in {@link #getModuleName}.
*
* @see #isPureJava
*/
private boolean forcePureJava;
/**
* The {@link Strategy} used by this test.
*/
private Strategy strategy;
/**
* A new instance of your subclass is constructed for each test method that is
* to be run. You should avoid running code in your subclass constructor,
* initializer blocks, and field initializations, because if those code blocks
* must be runnable outside of the GWT environment. As an example of what
* could go wrong if you run code there, trying to run a JSNI method could
* generate an {@link UnsatisfiedLinkError}, and trying to call
* {@link com.google.gwt.core.client.GWT#create(Class)} could throw an
* {@link UnsupportedOperationException}. Instead, override
* {@link #gwtSetUp()} and perform any initialization code there.
*/
public GWTTestCase() {
}
/**
* Determines whether or not exceptions will be caught by the test fixture.
* Override this method and return false
to let exceptions escape
* to the browser. This will break the normal JUnit reporting functionality,
* but can be useful in Production Mode with a JavaScript debugger to pin down
* where exceptions are originating.
*
* @return true
for normal JUnit behavior, or false
* to disable normal JUnit exception reporting
*/
public boolean catchExceptions() {
return true;
}
/**
* Specifies a module to use when running this test case. Subclasses must
* return the name of a module that will cause the source for that subclass to
* be included.
*
* @return the fully qualified name of a module, or null
to run
* as a pure Java (non-GWT) test case (same effect as passing
* true
to {@link #setForcePureJava})
*
* @see #isPureJava
*/
public abstract String getModuleName();
/**
* Get the {@link Strategy} to use when compiling and running this test.
*
* @return the test {@link Strategy}
*/
public Strategy getStrategy() {
if (strategy == null) {
strategy = new PropertyDefiningStrategy(this);
}
return strategy;
}
/**
* Get the synthetic module name, which includes the synthetic extension
* defined by the {@link Strategy}.
*
* @return the synthetic module name, or null
if this test case
* is run in pure Java mode (non-GWT)
*
* @see #isPureJava
*/
public final String getSyntheticModuleName() {
if (isPureJava()) {
return null;
} else {
return getModuleName() + "." + getStrategy().getSyntheticModuleExtension();
}
}
/**
* Returns whether this test case should be run in pure Java mode (non-GWT).
* Returns true
if and only if {@link #getModuleName} returns
* null
, or {@link #setForcePureJava} was last invoked with
* true
.
*/
public boolean isPureJava() {
return forcePureJava || (getModuleName() == null);
}
/**
* Stashes result
so that it can be accessed during
* {@link #runTest()}.
*/
@Override
public final void run(TestResult result) {
testResult = result;
super.run(result);
}
/**
* Specifies whether this test case should be always run in pure Java mode
* (non-GWT). Passing true
has the same effect as returning
* null
in {@link #getModuleName}. The setting is
* false
by default.
*
* @param forcePureJava true
to always run this test case in pure
* Java mode (non-GWT); false
to run this test case in GWT
* mode if {@link #getModuleName} does not return null
*
* @see #isPureJava
*/
public void setForcePureJava(boolean forcePureJava) {
this.forcePureJava = forcePureJava;
}
@Override
public void setName(String name) {
super.setName(name);
synchronized (ALL_GWT_TESTS_LOCK) {
// Once the name is set, we can add ourselves to the global set.
String syntheticModuleName = getSyntheticModuleName();
TestModuleInfo moduleInfo = ALL_GWT_TESTS.get(syntheticModuleName);
if (moduleInfo == null) {
// It should be safe to assume that tests with the same synthetic module
// name have the same test strategy. If they didn't, they would compile
// over each other.
moduleInfo = new TestModuleInfo(getModuleName(), syntheticModuleName,
getStrategy());
ALL_GWT_TESTS.put(syntheticModuleName, moduleInfo);
}
moduleInfo.getTests().add(
new TestInfo(syntheticModuleName, getClass().getName(), getName()));
}
}
/**
* Put the current test in asynchronous mode. If the test method completes
* normally, this test will not immediately succeed. Instead, a delay
* period begins. During the delay period, the test system will wait for
* one of three things to happen:
*
*
* - If {@link #finishTest()} is called before the delay period expires,
* the test will succeed.
* - If any exception escapes from an event handler during the delay
* period, the test will error with the thrown exception.
* - If the delay period expires and neither of the above has happened, the
* test will error with a {@link TimeoutException}.
*
*
*
* This method is typically used to test event driven functionality.
*
*
*
* Example:
* {@example com.google.gwt.examples.AsyncJUnitExample#testTimer()}
*
*
* @param timeoutMillis how long to wait before the current test will time out
* @tip Subsequent calls to this method reset the timeout.
* @see #finishTest()
*
* @throws UnsupportedOperationException if {@link #supportsAsync()} is false
*/
protected final void delayTestFinish(int timeoutMillis) {
// implemented in the translatable version of this class
}
/**
* Cause this test to succeed during asynchronous mode. After calling
* {@link #delayTestFinish(int)}, call this method during the delay period to
* cause this test to succeed. This method is typically called from an event
* handler some time after the test method returns control to the caller.
*
*
* Calling this method before the test method completes, will undo the effect
* of having called delayTestFinish()
. The test will revert to
* normal, non-asynchronous mode.
*
*
*
* Example:
* {@example com.google.gwt.examples.AsyncJUnitExample#testTimer()}
*
*
* @throws IllegalStateException if this test is not in asynchronous mode
* @throws UnsupportedOperationException if {@link #supportsAsync()} is false
*
* @see #delayTestFinish(int)
*/
protected final void finishTest() {
// implemented in the translatable version of this class
}
/**
* Reports an exception that might have escaped to the browser.
*
* This method is called by the test framework to report uncaught exceptions. The default
* implementation causes the test case to be reported as 'failed'. However in some rare situations
* where an uncaught exception is expected, a test case may choose to alter the behavior by
* overriding it:
*
* class SomeTestCase extends GWTTestCase {
*
* class ExpectedTestException extends Exception {}
*
* {@literal @}Override
* protected void reportUncaughtException(Throwable t) {
* // Ignore exceptions that are expected to be thrown by our test cases:
* if (t instanceof ExpectedTestException) {
* return;
* }
* super.reportUncaughtException(t);
* }
* ...
* }
*
*
* Note that this method will not cause the test case to fail immediately if the main test body is
* still executing. In that case, test will fail after the main body returns with the reported
* exception. If the test has already finished (fail or success) before this method is called,
* the reported exception is currently ignored, but this may result in an error in a future
* version of GWT.
*
* @see com.google.gwt.core.client.GWT#reportUncaughtException
*/
protected void reportUncaughtException(Throwable ex) {
// implemented in the translatable version of this class
}
/**
* A replacement for JUnit's {@link #setUp()} method. This method runs once
* per test method in your subclass, just before your each test method runs
* and can be used to perform initialization. Override this method instead of
* {@link #setUp()}. This method is run even in pure Java mode (non-GWT).
*
* @see #setForcePureJava
*/
protected void gwtSetUp() throws Exception {
}
/**
* A replacement for JUnit's {@link #tearDown()} method. This method runs once
* per test method in your subclass, just after your each test method runs and
* can be used to perform cleanup. Override this method instead of
* {@link #tearDown()}. This method is run even in pure Java mode (non-GWT).
*
* @see #setForcePureJava
*/
protected void gwtTearDown() throws Exception {
}
/**
* Runs the test via the {@link JUnitShell} environment. Do not override or
* call this method.
*/
@Override
protected void runTest() throws Throwable {
if (this.getName() == null) {
throw new IllegalArgumentException(
"GWTTestCases require a name; \""
+ this.toString()
+ "\" has none. Perhaps you used TestSuite.addTest() instead of addTestClass()?");
}
if (isPureJava()) {
super.runTest();
} else {
JUnitShell.runTest(this, testResult);
}
}
/**
* This method has been made final to prevent you from accidentally running
* client code outside of the GWT environment. Please override
* {@link #gwtSetUp()} instead.
*/
@Override
protected final void setUp() throws Exception {
if (isPureJava()) {
gwtSetUp();
}
}
/**
* This method has been made final to prevent you from accidentally running
* client code outside of the GWT environment. Please override
* {@link #gwtTearDown()} instead.
*/
@Override
protected final void tearDown() throws Exception {
if (isPureJava()) {
gwtTearDown();
}
}
}