Blog-Archiv

Freitag, 15. September 2023

JUnit 5 Before and After Annotations Execution Order

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 ....

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!




Keine Kommentare: