1

The following sample test generates the output as printed below.

Sample test:

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class OuterTest
{

   @BeforeEach
   void outerSetup()
   {
      System.out.println( "outerSetup" );
   }


   @AfterEach
   void outerTearDown()
   {
      System.out.println( "outerTearDown" );
   }


   @Test
   void outerTest1()
   {
      System.out.println( " outerTest1" );
   }


   @Test
   void outerTest2()
   {
      System.out.println( " outerTest2" );
   }

   @Nested
   class InnerTest
   {
      @BeforeEach
      void innerSetup()
      {
         System.out.println( " innerSetup" );
      }


      @AfterEach
      void innerTearDown()
      {
         System.out.println( " innerTearDown" );
      }


      @Test
      void innerTest1()
      {
         System.out.println( "  innerTest1" );
      }


      @Test
      void innerTest2()
      {
         System.out.println( "  innerTest2" );
      }


      @RepeatedTest(3)
      void innerRepeatedTest()
      {
         System.out.println( "  innerRepeatedTest" );
      }


      @ParameterizedTest
      @ValueSource(strings = { "foo", "bar", "baz" })
      void innerParameterizedTest( final String input )
      {
         System.out.println( "  innerParameterizedTest - " + input );
      }
   }
}

Output:

outerSetup
 outerTest1
outerTearDown
outerSetup
 outerTest2
outerTearDown
outerSetup
 innerSetup
  innerRepeatedTest
 innerTearDown
outerTearDown
outerSetup
 innerSetup
  innerRepeatedTest
 innerTearDown
outerTearDown
outerSetup
 innerSetup
  innerRepeatedTest
 innerTearDown
outerTearDown
outerSetup
 innerSetup
  innerParameterizedTest - foo
 innerTearDown
outerTearDown
outerSetup
 innerSetup
  innerParameterizedTest - bar
 innerTearDown
outerTearDown
outerSetup
 innerSetup
  innerParameterizedTest - baz
 innerTearDown
outerTearDown
outerSetup
 innerSetup
  innerTest1
 innerTearDown
outerTearDown
outerSetup
 innerSetup
  innerTest2
 innerTearDown
outerTearDown

For our use case we want to achieve, that the @BeforeEach/@AfterEach methods are only called before/after each test in class OuterTest and before the first resp. after the last test in class InnerTest, but not between the tests of the inner class.

So the desired output is the following:

outerSetup
 outerTest1
outerTearDown
outerSetup
 outerTest2
outerTearDown
outerSetup
 innerSetup
  innerRepeatedTest
 innerTearDown
 innerSetup
  innerRepeatedTest
 innerTearDown
 innerSetup
  innerRepeatedTest
 innerTearDown
 innerSetup
  innerParameterizedTest - foo
 innerTearDown
 innerSetup
  innerParameterizedTest - bar
 innerTearDown
 innerSetup
  innerParameterizedTest - baz
 innerTearDown
 innerSetup
  innerTest1
 innerTearDown
 innerSetup
  innerTest2
 innerTearDown
outerTearDown

I tried to change the behavior of the @BeforeEach/@AfterEach methods by implementing an extension, which implements an InvocationInterceptor, but I failed to find out if a test class is the last test in the inner class, which would enable to decide, if the @BeforeEach/@AfterEach methods of the outer class should be called or not.

Does anyone know how to achieve this?

Thanks in advance!

0x5F3759DF
  • 283
  • 3
  • 9

1 Answers1

0

If you are able to extract your actual outer setup and teardown methods, you can introduce a second @Nested class for the outer tests and use @BeforeAll and @AfterAll to invoke the outer setup/teardown once:

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class OuterTest {

    void actualOuterSetUp() {
        System.out.println("outerSetUp");
    }

    void actualOuterTearDown() {
        System.out.println("outerTearDown");
    }

    @Nested
    class InnerTestA {

        @BeforeEach
        void outerSetUp() {
            actualOuterSetUp();
        }


        @AfterEach
        void outerTearDown() {
            actualOuterTearDown();
        }

        @Test
        void outerTest1() {
            System.out.println(" outerTest1");
        }

        @Test
        void outerTest2() {
            System.out.println(" outerTest2");
        }

    }

    @Nested
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    class InnerTestB {

        @BeforeAll
        void outerSetUp() {
            actualOuterSetUp();
        }

        @AfterAll
        void outerTearDown() {
            actualOuterTearDown();
        }

        @BeforeEach
        void innerSetUp() {
            System.out.println(" innerSetUp");
        }


        @AfterEach
        void innerTearDown() {
            System.out.println(" innerTearDown");
        }


        @Test
        void innerTest1() {
            System.out.println("  innerTest1");
        }


        @Test
        void innerTest2() {
            System.out.println("  innerTest2");
        }

        @RepeatedTest(3)
        void innerRepeatedTest() {
            System.out.println("  innerRepeatedTest");
        }

        @ParameterizedTest
        @ValueSource(strings = {"foo", "bar", "baz"})
        void innerParameterizedTest(final String input) {
            System.out.println("  innerParameterizedTest - " + input);
        }

    }

}

Output (order of the nested class may vary on your system):

outerSetUp
 outerTest1
outerTearDown
outerSetUp
 outerTest2
outerTearDown
outerSetUp
 innerSetUp
  innerRepeatedTest
 innerTearDown
 innerSetUp
  innerRepeatedTest
 innerTearDown
 innerSetUp
  innerRepeatedTest
 innerTearDown
 innerSetUp
  innerParameterizedTest - foo
 innerTearDown
 innerSetUp
  innerParameterizedTest - bar
 innerTearDown
 innerSetUp
  innerParameterizedTest - baz
 innerTearDown
 innerSetUp
  innerTest1
 innerTearDown
 innerSetUp
  innerTest2
 innerTearDown
outerTearDown

If you are on Java 16+, you can also omit @TestInstance(TestInstance.Lifecycle.PER_CLASS) and use static instead.

beatngu13
  • 7,201
  • 6
  • 37
  • 66
  • Thanks for your reply! Sure, if one has a very simple outer setup and tearDown this is one possible approach. But what if one has a more complicated constellation like a class hierarchy in the outer class or something similar? – 0x5F3759DF Aug 05 '23 at 18:34
  • @mrwerner I find that hard to answer given the example code. Maybe the setup is better put in a dedicated extension than a class hierarchy. It really depends on the test (suite) design. Would you mind extending your example? – beatngu13 Aug 06 '23 at 16:09