I examined the life cycle of the extension of JUnit Jupiter

What is the life cycle of an Extension instance trying to write an Extension for JUnit 5 (JUnit Jupiter)? Since it became, it is a memorandum.

environment

TL;TR

--The instance registered with @Extension is shared for each test class, and if the test class is different, the Extension instance will be different. --Instance registered with @RegisterExtension --In the case of static field, it is shared for each test class. --For non-static fields, it depends on the life cycle of the test class.

Review of Extension Model

The JUnit 5 Extension Model is a mechanism for standardizing pre-processing, post-processing, exception handling, etc. for each test. Compared to Junit 4's Rule etc., the degree of freedom is lower, but you can easily create an extension just by implementing an interface corresponding to the extension point.

There are three ways to use Extension.

  1. Register the extension with @Extension.
  2. Register the extension programmatically with @RegisterExtension.
  3. Register automatically with Service Loader.

This time, use methods 1 and 2 to check when the Extension instance is created.

The extensions used for confirmation are as follows.

FooExtension.java


import org.junit.jupiter.api.extension.*;

public class FooExtension implements BeforeEachCallback, AfterEachCallback, BeforeAllCallback, AfterAllCallback {

    {
        System.out.println(String.format("[%s] created", this.toString()));
    }

    @Override
    public void beforeAll(ExtensionContext extensionContext) throws Exception {
        System.out.println(String.format("[%s] before all", this.toString()));
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        System.out.println(String.format("[%s] before each", this.toString()));
    }

    @Override
    public void afterEach(ExtensionContext extensionContext) throws Exception {
        System.out.println(String.format("[%s] after each", this.toString()));
    }

    @Override
    public void afterAll(ExtensionContext extensionContext) throws Exception {
        System.out.println(String.format("[%s] after all", this.toString()));
    }
}

Register Extension with @Extension

Source

@ExtendWith(FooExtension.class)
public class ExtensionTest {

    @Test
    void test1() { System.out.println("test 1"); }

    @Test
    void test2() { System.out.println("test 2"); }

    @Test
    void test3() { System.out.println("test 3"); }
}

result

[example.FooExtension@70325e14] created
[example.FooExtension@70325e14] before all
[example.FooExtension@70325e14] before each
test 1
[example.FooExtension@70325e14] after each
[example.FooExtension@70325e14] before each
test 2
[example.FooExtension@70325e14] after each
[example.FooExtension@70325e14] before each
test 3
[example.FooExtension@70325e14] after each
[example.FooExtension@70325e14] after all

You can see that each test shares one Extension instance. An instance of ExtensionTest is created for each test, but the same result was obtained when changing to share the instance of ExtensionTest with @TestInstance (Lifecycle.PER_CLASS).

@RegisterExtension + Register Extension with static field

Source

SecondExtensionTest.java


public class SecondExtensionTest {

    @RegisterExtension
    static FooExtension fooExtension = new FooExtension();

    @Test
    void test1() { System.out.println("test 1"); }

    @Test
    void test2() { System.out.println("test 2"); }

    @Test
    void test3() { System.out.println("test 3"); }
}

result

[example.FooExtension@55183b20] created
[example.FooExtension@55183b20] before all
[example.FooExtension@55183b20] before each
test 1
[example.FooExtension@55183b20] after each
[example.FooExtension@55183b20] before each
test 2
[example.FooExtension@55183b20] after each
[example.FooExtension@55183b20] before each
test 3
[example.FooExtension@55183b20] after each
[example.FooExtension@55183b20] after all

Of course, since it is a static field, the same instance is shared.

@RegisterExtension + Register Extension in non-static field

Let's remove the static of the fooExtension of the SecondExtensionTest earlier.

For Lifecycle.PER_METHOD

Source

SecondExtensionTest.java


public class SecondExtensionTest {

    @RegisterExtension
-   static FooExtension fooExtension = new FooExtension();
+   FooExtension fooExtension = new FooExtension();

    //Omission
}

result

[example.FooExtension@40a4337a] created
[example.FooExtension@40a4337a] before each
test 1
[example.FooExtension@40a4337a] after each
[example.FooExtension@fa4c865] created
[example.FooExtension@fa4c865] before each
test 2
[example.FooExtension@fa4c865] after each
[example.FooExtension@3bd82cf5] created
[example.FooExtension@3bd82cf5] before each
test 3
[example.FooExtension@3bd82cf5] after each

You can see that each test has a different instance of Extension. This is because an instance of SecondExtensionTest itself is created for each test. In addition, BeforeAll and AfterAll are not working, but in the case of Lifecycle.PER_METHOD as well as when adding @BeforeAll to the method, it must be static.

For Lifecycle.PER_CLASS

So, let's make the life cycle of Second Extension Test a form in which an instance is created only once.

Source

SecondExtensionTest.java


+ @TestInstance(Lifecycle.PER_CLASS)
public class SecondExtensionTest {

    @RegisterExtension
    FooExtension fooExtension = new FooExtension();

    //Omission
}

result

[example.FooExtension@c730b35] created
[example.FooExtension@c730b35] before all
[example.FooExtension@c730b35] before each
test 1
[example.FooExtension@c730b35] after each
[example.FooExtension@c730b35] before each
test 2
[example.FooExtension@c730b35] after each
[example.FooExtension@c730b35] before each
test 3
[example.FooExtension@c730b35] after each
[example.FooExtension@c730b35] after all

Extension instance is shared. Also, Before All and After All are now working. If you register an extension in a non-static field in this way, it naturally depends on the life cycle of the original test class.

If you read it properly, it will be written

https://junit.org/junit5/docs/current/user-guide/#extensions

Recommended Posts

I examined the life cycle of the extension of JUnit Jupiter
About the Android life cycle
I read the source of ArrayList I read
I read the source of Integer
I read the source of Long
I examined the concept of the process to understand how Docker works
I read the source of Short
I read the source of Byte
I read the source of String
I examined the flow of TCP communication with Spring Integration (client edition)
I examined the flow of TCP communication with Spring Integration (server edition)
I investigated the internal processing of Retrofit
[day: 5] I summarized the basics of Java
Summarize the life cycle of Java objects to be aware of in Android development
UCP-45060 The life cycle state is invalid. Check the status of the universal connection pool
I want to output the day of the week
I checked the place of concern of java.net.URL # getPath
I understood the very basics of character input
I compared the characteristics of Java and .NET
Overwrite the contents of config with Spring-boot + JUnit5
I want to var_dump the contents of the intent
I touched on the new features of Java 15
I tried using the profiler of IntelliJ IDEA
I checked the number of taxis with Ruby
Try the free version of Progate [Java I]