JUnit
has been a Java testing framework now for 25 years.
In continuation of my year 2020
article about JUnit 4 execution order
I will describe here how junit-jupiter-api 5.4.2 behaves,
together with junit-platform-commons 1.4.2.
It is about @BeforeEach
and @AfterEach
annotations, no others.
Questions will be: How is the execution order when ....
- the same annotation sits on multiple test methods
- several same annotations sit on multiple test methods
- the same annotations are used in super- and sub-class
- an annotation is missing on a method override
Same Annotation on Multiple Methods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class SameAnnotationOnMultipleMethods { @BeforeEach public void bbb() { System.err.println("@BeforeEach bbb()"); } @BeforeEach public void aaa() { System.err.println("@BeforeEach aaa()"); } @BeforeEach public void ccc() { System.err.println("@BeforeEach ccc()"); } @AfterEach public void zzz() { System.err.println("@AfterEach zzz()"); } @AfterEach public void xxx() { System.err.println("@AfterEach xxx()"); } @AfterEach public void yyy() { System.err.println("@AfterEach yyy()"); } @Test public void test() { System.err.println("SameAnnotationOnMultipleMethods: @Test test()"); } } |
Output is:
@BeforeEach aaa() @BeforeEach bbb() @BeforeEach ccc() SameAnnotationOnMultipleMethods: @Test test() @AfterEach xxx() @AfterEach yyy() @AfterEach zzz()
The methods seem to get executed in alphabetical order, not as written in source.
This applies to both @BeforeEach
and @AfterEach
.
Multiple Same Annotations on Multiple Methods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class SameMultipleAnnotationsOnMultipleMethods { @BeforeEach @AfterEach public void bbb() { System.err.println("@BeforeEach @AfterEach bbb()"); } @BeforeEach @AfterEach public void aaa() { System.err.println("@BeforeEach @AfterEach aaa()"); } @BeforeEach @AfterEach public void ccc() { System.err.println("@BeforeEach @AfterEach ccc()"); } @Test public void test() { System.err.println("SameMultipleAnnotationsOnMultipleMethods: @Test test()"); } } |
Output is:
@BeforeEach @AfterEach aaa() @BeforeEach @AfterEach bbb() @BeforeEach @AfterEach ccc() SameMultipleAnnotationsOnMultipleMethods: @Test test() @BeforeEach @AfterEach aaa() @BeforeEach @AfterEach bbb() @BeforeEach @AfterEach ccc()
Again the methods seem to get executed in alphabetical order.
An interesting variant of this ambiguity is when @BeforeEach
and @AfterEach
collide with a single @BeforeEach
or @AfterEach
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class SameMultipleAnnotationsOnMultipleMethods { @BeforeEach public void bbb() { System.err.println("@BeforeEach bbb()"); } @BeforeEach @AfterEach public void aaa() { System.err.println("@BeforeEach @AfterEach aaa()"); } @AfterEach public void ccc() { System.err.println("@AfterEach ccc()"); } @Test public void test() { System.err.println("SameMultipleAnnotationsOnMultipleMethods: @Test test()"); } } |
Output is:
@BeforeEach @AfterEach aaa() @BeforeEach bbb() SameMultipleAnnotationsOnMultipleMethods: @Test test() @BeforeEach @AfterEach aaa() @AfterEach ccc()
Again the execution order is alphabetical by method-name.
The order is not connected to the fact that aaa()
carries multiple annotations,
as I found out by changing the method names.
Annotations in Inheritance
Super-class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | abstract class AbstractBeforeAndAfter { @BeforeEach public void setUp() { System.err.println("AbstractBeforeAndAfter: @BeforeEach setUp()"); } @BeforeEach @AfterEach public void bothInSuperclass() { System.err.println("AbstractBeforeAndAfter: @BeforeEach @AfterEach bothInSuperclass()"); } @AfterEach public void tearDown() { System.err.println("AbstractBeforeAndAfter: @AfterEach tearDown()"); } } |
Derived class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class AnnotationsInInheritance extends AbstractBeforeAndAfter { @BeforeEach @Override public void setUp() { System.err.println("AnnotationsInInheritance: @BeforeEach @Override setUp()"); } @BeforeEach @AfterEach public void bothInDerivedClass() { System.err.println("AnnotationsInInheritance: @BeforeEach @AfterEach bothInDerivedClass()"); } @AfterEach @Override public void tearDown() { System.err.println("AnnotationsInInheritance: @AfterEach @Override tearDown()"); } @Test public void test() { System.err.println("AnnotationsInInheritance: @Test test()"); } } |
Mind that bothInDerivedClass()
is not an override of
bothInSuperClass()
!
Output is:
AbstractBeforeAndAfter: @BeforeEach @AfterEach bothInSuperclass() AnnotationsInInheritance: @BeforeEach @AfterEach bothInDerivedClass() AnnotationsInInheritance: @BeforeEach @Override setUp() AnnotationsInInheritance: @Test test() AnnotationsInInheritance: @AfterEach @Override tearDown() AnnotationsInInheritance: @BeforeEach @AfterEach bothInDerivedClass() AbstractBeforeAndAfter: @BeforeEach @AfterEach bothInSuperclass()
Super-class @BeforeEach
annotations come before derived class.
Super-class @AfterEach
annotations come after derived class.
Mind that "@AfterEach @Override tearDown()
" here comes before
"@BeforeEach @AfterEach bothInDerivedClass()
".
This contradicts the previous assumption that methods are executed in alphabetical order.
The tearDown()
method is executed first because it is an @Override of a method in super-class.
Missing Annotation in Override
This builds on the same super-class as the example above.
Assumption is that @AfterEach tearDown()
in super-class was overridden,
but the annotation was forgotten:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class MissingAnnotationInOverride extends AbstractBeforeAndAfter { @Override // Mind that @AfterEach is missing here! public void tearDown() { System.err.println("MissingAnnotationInOverride: @Override tearDown()"); } @Test public void test() { System.err.println("MissingAnnotationInOverride: @Test test()"); } } |
Output is:
AbstractBeforeAndAfter: @BeforeEach @AfterEach bothInSuperclass() AbstractBeforeAndAfter: @BeforeEach setUp() MissingAnnotationInOverride: @Test test() AbstractBeforeAndAfter: @BeforeEach @AfterEach bothInSuperclass()
The tearDown()
was not executed.
That means when you override an annotated method and do not call it explicitly,
the annotation in super-class will be ignored and the method will not be executed at all.
In other words, these JUnit 5 annotations are not inherited from a super-class, they have to be repeated.
Mind that such does not apply to any annotation-framework, it depends on the framework's implementation. Although the annotation definition syntax has become part of the Java programming language, the semantic of an annotation is up to its implementation, and that is NOT part of the Java runtime library.
Conclusion
Adding the annotations feature to the Java programming-language opened the door for complexity that has nothing to do with object-oriented thinking. We need to learn and try out the semantics of each and every annotation we use.
For the discussed JUnit5 annotations, we need to remember:
- Super-class annotations come first on "BeforeEach", come last on "AfterEach"
- Alphabetical order of execution is not something you should build upon, this would just lead to confusion
- The discussed JUnit-5 annotations will not be inherited from an overridden method, they must be repeated in the override
Hope this was helpful for some test developers!