0x5F3759DF
0x5F3759DF

Reputation: 393

How to suppress execution of @BeforeEach and @AfterEach methods of outer class between tests of inner (nested) class

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;

public class OuterTest extends ParentTest implements TestInterface1, TestInterface2
{
   @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 );
      }
   }
}
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

public class ParentTest
{
   @BeforeEach
   void parentSetup()
   {
      System.out.println( "parentSetup" );
   }


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

}
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

public interface TestInterface1
{
   @BeforeEach
   default void interface1Setup()
   {
      System.out.println( "interface1Setup" );
   }


   @AfterEach
   default void interface1TearDown()
   {
      System.out.println( "interface1TearDown" );
   }
}
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

public interface TestInterface2
{
   @BeforeEach
   default void interface2Setup()
   {
      System.out.println( "interface2Setup" );
   }


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

Output:

parentSetup
interface1Setup
interface2Setup
 outerSetup
  outerTest1
 outerTearDown
interface1TearDown
interface2TearDown
parentTearDown
parentSetup
interface1Setup
interface2Setup
 outerSetup
  outerTest2
 outerTearDown
interface1TearDown
interface2TearDown
parentTearDown
parentSetup
interface1Setup
interface2Setup
 outerSetup
  innerSetup
   innerRepeatedTest
  innerTearDown
 outerTearDown
interface1TearDown
interface2TearDown
parentTearDown
parentSetup
interface1Setup
interface2Setup
 outerSetup
  innerSetup
   innerRepeatedTest
  innerTearDown
 outerTearDown
interface1TearDown
interface2TearDown
parentTearDown
parentSetup
interface1Setup
interface2Setup
 outerSetup
  innerSetup
   innerRepeatedTest
  innerTearDown
 outerTearDown
interface1TearDown
interface2TearDown
parentTearDown
parentSetup
interface1Setup
interface2Setup
 outerSetup
  innerSetup
   innerParameterizedTest - foo
  innerTearDown
 outerTearDown
interface1TearDown
interface2TearDown
parentTearDown
parentSetup
interface1Setup
interface2Setup
 outerSetup
  innerSetup
   innerParameterizedTest - bar
  innerTearDown
 outerTearDown
interface1TearDown
interface2TearDown
parentTearDown
parentSetup
interface1Setup
interface2Setup
 outerSetup
  innerSetup
   innerParameterizedTest - baz
  innerTearDown
 outerTearDown
interface1TearDown
interface2TearDown
parentTearDown
parentSetup
interface1Setup
interface2Setup
 outerSetup
  innerSetup
   innerTest1
  innerTearDown
 outerTearDown
interface1TearDown
interface2TearDown
parentTearDown
parentSetup
interface1Setup
interface2Setup
 outerSetup
  innerSetup
   innerTest2
  innerTearDown
 outerTearDown
interface1TearDown
interface2TearDown
parentTearDown

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:

parentSetup
interface1Setup
interface2Setup
 outerSetup
  outerTest1
 outerTearDown
interface1TearDown
interface2TearDown
parentTearDown
parentSetup
interface1Setup
interface2Setup
 outerSetup
  outerTest2
 outerTearDown
interface1TearDown
interface2TearDown
parentTearDown
parentSetup
interface1Setup
interface2Setup
 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
interface1TearDown
interface2TearDown
parentTearDown

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!

Upvotes: 1

Views: 330

Answers (1)

beatngu13
beatngu13

Reputation: 9393

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.

Upvotes: 0

Related Questions