-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
OLD Prototype Writing Test Cases
Table of Contents
- Annotations
- Standard Test Class
- Custom Names
- Assertions
- Assumptions
- Disabling Tests
- Tagging and Filtering
- Nested Tests
- Method Parameters and Dependency Injection
JUnit 5 supports the following annotations for configuring tests and extending the framework.
All core annotations are located in the org.junit.gen5.api
package in the junit5-api
module.
Annotation | Description |
---|---|
@Test |
Denotes that a method is a test method. Unlike JUnit 4's @Test annotation, this annotation does not declare any attributes, since test extensions in JUnit 5 operate based on their own dedicated annotations. |
@TestInstance |
Used to configure the lifecycle of a test instance. By default a test instance will be created for each test method within a class. Annotate your test class with @TestInstance(PER_CLASS) to override the default behavior and have the test instance retained across test methods. |
@Name |
Declares a custom display name for the test class or test method |
@TestName |
Allows the display name of the current test to be supplied as a method parameter to @Test , @BeforeEach , and @AfterEach methods; analogous to the JUnit 4's TestName rule |
@BeforeEach |
Denotes that the annotated method should be executed before each @Test method in the current class or class hierarchy |
@AfterEach |
Denotes that the annotated method should be executed after each @Test method in the current class or class hierarchy |
@BeforeAll |
Denotes that the annotated method should be executed before all @Test methods in the current class or class hierarchy; analogous to JUnit 4's @BeforeClass . Such methods must be static unless the test class is annotated with @TestInstance(PER_CLASS) . |
@AfterAll |
Denotes that the annotated method should be executed after all @Test methods in the current class or class hierarchy; analogous to JUnit 4's @AfterClass . Such methods must be static unless the test class is annotated with @TestInstance(PER_CLASS) . |
@Nested |
Denotes that the annotated class is a nested test class; often - but not necessarily - used in conjunction with @TestInstance(PER_CLASS) . |
@Tag and @Tags
|
Used to declare tags for filtering tests, either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4 |
@Conditional |
Used to declare conditions that will be evaluated to determine if a test is enabled. @Disabled is a built-in implementation of conditional test execution. |
@Disabled |
Used to disable a test class or test method; analogous to JUnit 4's @Ignore
|
@ExtendWith |
Used to register custom extensions for tests such as MethodParameterResolver . See the page on test extensions
|
JUnit 5 annotations can be used as meta-annotations. That means that you can define your own composed annotation that will automatically inherit the semantics of its meta-annotations.
For example, instead of copying and pasting @Tag("fast")
throughout your code base (see Tagging and Filtering), you can create a custom composed annotation named @Fast
as
follows. @Fast
can then be used as a drop-in replacement for @Tag("fast")
.
import org.junit.gen5.api.*;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
public @interface Fast {
}
import org.junit.gen5.api.*;
@TestInstance(PER_CLASS)
class MyTest {
@BeforeAll
void initAll() {}
@BeforeEach
void init() {}
@Test
void succeedingTest() {}
@AfterEach
void tearDown() {}
@AfterAll
void tearDownAll() {}
}
Notice that neither the test class nor the test method need to be public
.
Also, @BeforeAll
and @AfterAll
can be used on non-static methods if the
test class is annotated with @TestInstance(PER_CLASS)
.
Test classes and test methods can declare a custom name -- with spaces, special characters, and even emojis -- that will be displayed by test runners and test reporting:
import org.junit.gen5.api.*;
@Name("A special test case")
class CanHaveAnyNameTest {
@Test
@Name("A nice name, isn't it?")
void testWithANiceName() {}
}
JUnit 5 comes with many of the assertion methods that JUnit 4 has and adds a few
that lend themselves well to being used with Java 8 lambdas. All JUnit 5 assertions
are static methods in the org.junit.gen5.Assertions
class.
import static org.junit.gen5.api.Assertions.*;
import org.junit.gen5.api.*;
class MyTest {
@Test
void standardAssertions() {
assertEquals(2, 2);
assertEquals(4, 4, "The optional assertion message is now the last parameter.");
assertTrue(2 == 2, () -> "Assertion messages can be lazily evaluated -- " +
"to avoid constructing complex messages unnecessarily.");
}
@Test
void groupedAssertions() {
// In a grouped assertion all assertions are executed, and any
// failures will be reported together.
assertAll("address",
() -> assertEquals("Johannes", address.getFirstName()),
() -> assertEquals("Link", address.getLastName())
);
}
@Test
void exceptionTesting() {
Throwable exception = expectThrows(IllegalArgumentException.class,
() -> throw new IllegalArgumentException("a message")
);
assertEquals("a message", exception.getMessage());
}
}
JUnit 5 comes with a subset of the assumption methods that JUnit 4 provides and adds a few
that lend themselves well to being used with Java 8 lambdas. All JUnit 5 assumptions
are static methods in the org.junit.gen5.Assumptions
class.
import static org.junit.gen5.api.Assertions.*;
import static org.junit.gen5.api.Assumptions.*;
import org.junit.gen5.api.*;
class MyTest {
@Test
void testOnlyOnCiServer() {
assumeTrue("CI".equals(System.getenv("ENV"));
// remainder of test
}
@Test
void testOnlyOnDeveloperWorkstation() {
assumeTrue("DEV".equals(System.getenv("ENV"),
() -> "Aborting test: not on developer workstation");
// remainder of test
}
@Test
void testInAllEnvironments() {
assumingThat("CI".equals(System.getenv("ENV"), () -> {
// perform these assertions only on the CI server
assertEquals(...);
});
// perform these assertions in all environments
assertEquals(...);
}
}
Here´s a disabled test case:
import org.junit.gen5.api.*;
@Disabled
class MyTest {
@Test
void testWillBeSkipped() {}
}
And here´s a test case with a disabled test method:
import org.junit.gen5.api.*;
class MyTest {
@Disabled
@Test
void testWillBeSkipped() {}
@Test
void testWillBeExecuted() {}
}
Test classes and methods can be tagged. Those tags can later be used to filter test discovery and execution:
import org.junit.gen5.api.*;
@Tag("fast")
@Tag("model")
class FastModelTests {
@Test
@Tag("taxes")
void testingTaxCalculation() {}
}
Nested tests give the test writer more capabilities to express the relationship among several group of tests. Here´s a somewhat contrived example:
import org.junit.gen5.api.*;
class MyObjectTest {
MyObject myObject;
@BeforeEach
void init() {
myObject = new MyObject();
}
@Test
void testEmptyObject() {}
@Nested
class WithChildren() {
@BeforeEach
void initWithChildren() {
myObject.addChild(new MyObject());
myObject.addChild(new MyObject());
}
@Test
void testObjectWithChildren() {}
}
}
Notice that only non-static inner classes can serve as nested tests. Nesting can be arbitrarily deep and those inner classes can be considered as full members of the test class family.
For a more meaningful example have a look at TestingAStack.
In all prior JUnit versions, @Test
, @BeforeEach
, and @AfterEach
methods were not allowed to have parameters (at least not with the standard Runner
implementations). As one of the major changes in JUnit 5, methods are now permitted to have parameters allowing for greater flexibility and enabling method-level Dependency Injection.
There are a few built-in resolvers in the prototype that need not be explicitly enabled:
-
@TestName
: If a method parameter is of typeString
and annotated with@TestName
, theTestNameParameterResolver
will supply the display name of the current test at runtime (either its canonical name or its user-provided@Name
). This acts as a drop-in replacement for theTestName
rule from JUnit 4:import org.junit.gen5.api.*; class MyTest { @BeforeEach void init(@TestName name) { assertTrue(name.equals("TEST 1") || name.equals("test2")); } @Test @Name("TEST 1") void test1(@TestName name) { assertEquals("TEST 1", name); } @Test void test2() {} }
All other parameter resolvers must be explicitly enabled by registering a test extension via @ExtendWith
.
-
Check out the
methodInjectionTest(...)
test method inSampleTestCase
for an example that uses the built-inTestNameParameterResolver
as well as two user-provided resolvers,CustomTypeParameterResolver
andCustomAnnotationParameterResolver
. -
The
MockitoExtension
is another example of aMethodParameterResolver
. While not intended to be production-ready, it demonstrates the simplicity and expressiveness of both the extension model and the parameter resolution process. Check out the source code forMockitoExtensionInBaseClassTest
for an example of injecting Mockito mocks into@BeforeEach
and@Test
methods:
import org.junit.gen5.api.*;
import static org.mockito.Mockito.when;
import com.example.mockito.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class MyMockitoTest {
@BeforeEach
void init(@InjectMock MyType myType) {
when(myType.getName()).thenReturn("hello");
}
@Test
void simpleTestWithInjectedMock(@InjectMock MyType myType) {
assertEquals("hello", myType.getName());
}
}