blob: 719bbd924215cecf4c7750c3b0439effa385e06c [file] [log] [blame] [view]
# JUnit 4 Migration
As of Android 24 (N), JUnit3 style javatests have been deprecated for the new
JUnit4-based [Android Testing Support Library][1].
We are in the process of changing all instrumentation tests in chromium to
JUnit4 style. This doc explains the differences between JUnit3 and JUnit4
instrumentation tests and how to write or convert them.
[TOC]
## Differences between JUnit3 and JUnit4 instrumentation tests
| | JUnit3 | JUnit4 |
|--------------|-----------------------------------------------------|------------------------------------------|
| Inheritance | Tests extends TestCase or child classes | No inheritance. |
| Test methods | methods named with test prefix | methods annotated with @Test |
| Set up | setUp() method | public method annotated with @Before |
| Tear down | tearDown() method | public method annotated with @After |
| Test runner | declared within test apk AndroidManifest.xml | Must specify `chromium-junit4:"true"` |
| Class runner | N/A | @RunWith(XClassRunner.class) |
| Assertion | Extends from junit.framework.Assert, inherited APIs | Use static methods from org.junit.Assert |
> Please note that during the migration, we support running JUnit3 and JUnit4
> tests in the same apk. This requires two tags, one each for JUnit3 and JUnit4.
> The tag for the JUnit4 runner must specify `chromium-junit4:"true"`
> ([Example][2])
- **Other JUnit4 features**:
- Tests can be annotated to expect an exception, e.g.
`@Test(expected=MyException.class)`. Tests annotated this way will
fail if they do not throw the given exception.
- **Test suite set up**: public static method annotated with `@BeforeClass`
- **Test suite tear down**: public static method annotated with
`@AfterClass`
- **Replacement for JUnit3 test base classes**
- [`TestRule`][3]:
- TestRule is a class to **outsource your test setUp, tearDown, and
utility methods**. Since there are no more interitance and TestBase classes,
one should use TestRule for any API calls provided by its test base classes
previously.
- One test can declare multiple TestRules and the class runner will run all of
them. If the order of the TestRule matters to you, use
[`RuleChain`][8]
- [`ActivityTestRule`][4]
- `ActivityTestRule` is a special `TestRule` provided by Android Testing
Support Library that allows tests to launch an Activity.
([Documentation][4])
## Example Code of JUnit3 test and JUnit4 test
JUnit3:
```java
public class MyTestClass extends MyActivityInstrumentationTestCase2<TestActivity> {
@Override
protected void setUp(){
super.setUp();
setActivityIntent(new Intent());
getActivity();
}
@Override
protected void tearDown() {
specialActionFromSuper();
super.tearDown();
}
public void testA() {
assertEquals(1, 1);
}
}
```
JUnit4:
```java
@RunWith(BaseJUnit4ClassRunner.class);
public class TestClass {
@Rule public ActivityTestRule<TestActivity> mRule = new ActivityTestRule<>(TestActivity.class);
@Before
public void setUp() { //Must be public
mRule.launchActivity(new Intent());
}
@After
public void tearDown() { //Must be public
mRule.specialActionFromActivityTestRule();
}
@Test
public void testA() {
Assert.assertEquals(1, 1);
}
}
```
## Migration process
1. Add required libraries to your target dependencies in BUILD.gn
- JUnit 4 library: `//third_party/junit`
- Android Testing Support Rules:
- `//third_party/android_support_test_runner:runner_java`
(for `AndroidJUnitRunner`, etc)
- `//third_party/android_support_test_runner:rules_java`
(for `ActivityTestRule`, etc)
1. Add class runner to your test apk manifest.
([example][2])
- Keep in mind you can have multiple instrumentations in your manifest.
Our test runner will run JUnit4 tests with JUnit4 runner and JUnit3
tests with non-JUnit4 runner.
1. Refactor TestBase class to a TestRule class.
([example CL](https://codereview.chromium.org/2632043002))
- +yolandyan will do this part, however, if you did refactoring yourself,
please add him as a reviewer for your CL and enjoy his eternal appreciation!
1. Use [auto migrate script][5] to or manually convert all JUnit3 tests to
JUnit4 style in a your javatest directory
- we understand it's tedious to just manually write all the annotations,
change modifiers, etc to convert all the javatest, so we created an auto
change script that helps you to convert all the javatests in a certain
directory. Please check its [README page][5]
on instructions.
## Customized TestRule example
TestRule:
```java
public class MyRule implements TestRule {
// 1: Add utility methods...
@Override
public Statement apply(final Statement base, Description desc) {
return new Statement() {
@Override
public void evaluate() {
// 2: Code here runs before @Before method
base.evaluate()
// 3: Code here runs after @After method
}
}
}
}
```
## Command Line Flags
In our Junit3 tests command line flags (set by the CommandLineFlag annotations) were inherited from the
test base classes. As an example, ChromeActivityTestBase is annotated with:
```java
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ...
```
and as a result any test in a class derived from ChromeActivityTestBase will disable the first run experience.
The Junit4 tests classes are not however, derived from test base classes; instead their behavior is defined by
test rules. To support this our Junit4 test runner will examine the command line flag annotations on all rules
referenced with @Rule annotations in the test class. In addition, where one rule is derived from another, the
command line flags propogate through the hierarchy of rules. See, for example, [BottomSheetTestRule][11]
Note:- This has only recently been implemented, so is not yet used in all tests. See [this bug][12]
The CommandLineFlags annonations are more fully documented in the [CommandLineFlags class][13]
## Common Errors
1. Instrumentation tests that rely on test thread to have message handler
will not work. For example error message:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
or
java.lang.IllegalStateException: The current thread must have a looper!
Please utilize `ActivityTestRule.runOnUiThread(Runnable r)` to refactor
these tests. For more, check this [GitHub issue][6]
1. Use `@UiThreadTest` with caution!!
- Currently, **@UiThreadTest is only effective when UiThreadTestRule or
ActivityTestRule is declared** in the test class.
- Please use **`android.support.test.annotation.UiThreadTest`, NOT
`android.test.UiThreadTest`**.
- When using @UiThreadTest, **it would cause `setUp` and `tearDown` to
run in Ui Thread** as well. Avoid that by calling [`runOnUiThread`][9]
or [`runOnMainSync`][10] with a Runnable.
```java
// Wrong test
public class Test {
@Rule
public ActivityTestRule<MyActivity> mRule = new ActivityTestRule<>(
MyActivity.class>
@Before
public void setUp() {
// Cause failure because this also runs on Ui Thread, while it
// is intended for Instrumentation worker thread
mRule.launchActivity()
}
@UiThreadTest
public void test() {
actionThatNeedsUiThread();
}
}
```
The correct thing to do is
```java
// Correct test
public class Test {
@Rule
public ActivityTestRule<MyActivity> mRule = new ActivityTestRule<>(
MyActivity.class>
@Before
public void setUp() {
mRule.launchActivity()
}
public void test() {
mRule.runOnUiThread(new Runnable() {
@Override
public void run() {
actionThatNeedsUiThread();
}
});
}
}
```
1. `assertEquals(float a, float b)` and `assertEquals(double a, double b)` are
deprecated in JUnit4's Assert class. **Despite only generating a warning at
build time, they fail at runtime.** Please use
`Assert.assertEquals(float a, float b, float delta)`
1. Errorprone expects all public methods starting with `test...` to be
annotated with `@Test`. Failure to meet that expectation will cause
errorprone to fail with something like this:
[JUnit4TestNotRun] Test method will not be run; please add @Test annotation
In particular, you may see this when attempting to disable tests. In that
case, the test should be annotated with both @DisabledTest and @Test.
## Common questions
- Q: Are `@Test` and `@LargeTest/@MediumTest/@SmallTest` annotation both
necessary?
- A: Yes, both are required for now. We plan to refactor this in the
future.
- Q: Isn't the inheritance of the Test classes just migrated to inheritance
of TestRules?
- A: Yes. During the migration, we plan to maintain a 1:1 mapping between
the test base classes and TestRules (e.g. ContentShellTestBase to
ContentShellTestRule in this
[CL](https://codereview.chromium.org/2632043002)).
This allows the auto convert script to replace API calls in any
JUnit3 tests. After the migration, we plan to refactor the TestRules to
be more modular.
If you have any other questions, feel free to report in [this bug][7].
## Links and Crbugs
- [Android Test Support Library documentation][1]
- [Auto change script][5]
- [Crbug for JUnit3 to JUnit4 migration][7]
[1]: https://developer.android.com/topic/libraries/testing-support-library/index.html
[2]: https://cs.chromium.org/chromium/src/android_webview/tools/system_webview_shell/layout_tests/AndroidManifest.xml?l=36
[3]: http://junit.org/junit4/javadoc/4.12/org/junit/rules/TestRule.html
[4]: https://developer.android.com/reference/android/support/test/rule/ActivityTestRule.html
[5]: https://github.com/yoland68/chromium-junit-auto-migrate
[6]: http://github.com/skyisle/android-test-kit/issues/121
[7]: https://bugs.chromium.org/p/chromium/issues/detail?id=640116
[8]: http://junit.org/junit4/javadoc/4.12/org/junit/rules/RuleChain.html
[9]: https://developer.android.com/reference/android/app/Instrumentation.html#runOnMainSync(java.lang.Runnable)
[10]: https://developer.android.com/reference/android/support/test/rule/UiThreadTestRule.html#runOnUiThread(java.lang.Runnable)
[11]: /chrome/test/android/javatests/src/org/chromium/chrome/test/BottomSheetTestRule.java
[12]: https://bugs.chromium.org/p/chromium/issues/detail?id=734553
[13]: /base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java