Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document order of callback methods and extensions that run after test/container execution #1620

Closed
8 tasks done
apodkutin opened this issue Oct 5, 2018 · 28 comments
Closed
8 tasks done

Comments

@apodkutin
Copy link

apodkutin commented Oct 5, 2018

Based on JUnit5 user-guide extensions execution order should correlate with extensions declaration order:

Extensions registered declaratively via @ExtendWith will be executed in the order in which they are declared in the source code. For example, the execution of tests in both MyFirstTests and MySecondTests will be extended by the FooExtension and BarExtension, in exactly that order.

But it does not.

Example: https://github.com/apodkutin/junit5-extensions-order-test/blob/master/src/test/java/junit5/extensions/order/JUnit5ExtensionsOrderTest.java

@ExtendWith({FirstExtension.class, SecondExtension.class})

Expected output

FirstExtension afterEach method call
SecondExtension afterEach method call

Actual output

SecondExtension afterEach method call
FirstExtension afterEach method call

Jupiter-api version is 5.3.1

<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>

Deliverables

  • Document the lack of explicit ordering for all user-supplied callback methods (i.e., @BeforeEach, @AfterEach, @BeforeAll, @AfterAll) declared within a single test class/interface.

    • in Javadoc
    • in User Guide
  • Document the lack of wrapping behavior for all user-supplied callback methods (e.g., @AfterAll, @AfterEach) declared within a single test class/interface.

    • in Javadoc
    • in User Guide
  • Document wrapping behavior for all user-supplied callback methods (i.e., @BeforeEach, @AfterEach, @BeforeAll, @AfterAll) within test class/interface hierarchies.

    • in Javadoc
    • in User Guide
  • Document wrapping behavior for all applicable extension APIs (e.g., TestExecutionExceptionHandler, AfterTestExecutionCallback, AfterEachCallback, AfterAllCallback, etc.).

    • in Javadoc
    • in User Guide
@smoyer64
Copy link
Contributor

smoyer64 commented Oct 5, 2018

That looks correct to me ... it's assumed that AfterEach is often paired with BeforeEach. The order of post-test extension execution is inverted so that there's a symmetry around the test. See Extensions Execution Order in the Users Guide. This diagram doesn't show exactly what happens if more than one of each extension is present but the idea is that the extensions run after the test should be in the reverse order of their counterparts run before the test.

@apodkutin
Copy link
Author

Thank for your comment @smoyer64, but I can't agree with you, because you're showing the diagram which is related to user-provided test and lifecycle methods and it looks like expected behavior for me as well.

But when I'm extending some test class with extension, and I'm declaring them in a specific order, I'm expecting that they will be invoked in the same order, I mean beforeEach from the first extension and after that beforeEach from the second extension. And I'm expecting that the same behavior should be for afterEach method based only on this statement from the guide "will be executed in the order in which they are declared in the source code."

I understand that for more than one extension order, execution order logic will be the same as for user-provided test and lifecycle methods execution order logic. But, do you think, that this statement is correct "will be executed in the order in which they are declared in the source code."? Maybe it should be changed to avoid misleading or maybe I've understood it wrong.

@marcphilipp
Copy link
Member

@apodkutin Thanks for reporting this issue! @smoyer64 described our intention correctly: Extensions that run after a test are run in reverse order. However, as you noticed that's missing from the documentation and we should add it there.

@marcphilipp marcphilipp changed the title Extensions execution order does not work as expected Document order of extensions for callbacks that run after test/container execution Oct 6, 2018
@marcphilipp marcphilipp added this to the 5.4 M1 milestone Oct 6, 2018
@sbrannen sbrannen changed the title Document order of extensions for callbacks that run after test/container execution Document order of callback methods and extensions that run after test/container execution Oct 6, 2018
@sbrannen
Copy link
Member

sbrannen commented Oct 6, 2018

Please note that the wrapping behavior for "after" extensions applies to callback methods as well (e.g., @AfterEach, @AfterAll).

I've updated the title of this issue accordingly.

@sbrannen
Copy link
Member

sbrannen commented Oct 6, 2018

I've added a Deliverables section as well.

@apodkutin
Copy link
Author

@smoyer64 , @marcphilipp @sbrannen, thank you guys for the quick answers!
I'm really appreciate that you will add this information to the JavaDoc and to the user-guide as well.

But I still have only one question, do you think that this behavior and I'm talking only about callback methods from extensions should be like this? I have one example when I've faced it and why I actually raised this ticket.

Example:

We have EnvVariablesExtension which is changing environment variables in the beforeEach callback method and restoring them to original ones in the afterEach callback method.
I've tried to test EnvVariablesExtension with the second extension. Let's name it AcceptanceExtension and, of course, this extension is saving original environment variables in the beforeEach callback and asserting that environment variables which was fetched in afterEach callback method equals original environment variables.

Order of the extensions is

  1. AcceptanceExtension
  2. EnvVariablesExtension

As you already understood, beforeEach part works fine, but the second one afterEach part does not work, because AcceptanceExtension is called first and assertion failed.

My question is, why you've chosen this approach for execution order of callback extension's methods?
Why not just calling every callback extension's methods in that order which you've provided in the @ExtendWith({AcceptanceExtension.class, EnvVariablesExtension.class}) annotation?

AcceptanceExtension.beforeEach -> 
EnvVariablesExtension.beforeEach -> 
Test's body -> 
AcceptanceExtension.beforeEach -> 
EnvVariablesExtension.beforeEach

Thank you in advance.

@smoyer64
Copy link
Contributor

smoyer64 commented Oct 7, 2018

I've never had a situation where I didn't want to invert the test setup when performing the tear-down. In my mind, the phrase

Let's name it AcceptanceExtension and, of course, this extension is saving original environment variables in the beforeEach callback and asserting that environment variables which was fetched in afterEach callback method equals original environment variables.

immediately raised red-flags. In my opinion, you really shouldn't be making assertions outside of test methods. In the case where I've written tests for an extension that implements both a beforeXxx() and afterXxx() extension point, I've individually tested that the beforeXxx() method functions as expected and that the afterXxx() method works as expected and then trusted that JUnit 5 will execute them at the appropriate times in the test lifecycle.

@apodkutin
Copy link
Author

apodkutin commented Oct 7, 2018

I agree with you @smoyer64 that it is a better way to test your custom extensions. But main thoughts were not about approach of testing your extensions, it was just an example to give us food for thought.

I'm just wondering maybe there are other cases where you need the same strict declared order of execution's callback methods calls, not only for beforeEach callback methods, but for afterEach callback methods as well.

@marcphilipp marcphilipp modified the milestones: 5.4 M1, 5.4 M2 Nov 23, 2018
@sormuras sormuras modified the milestones: 5.4 RC1, 5.4 GA Jan 11, 2019
@sormuras
Copy link
Member

sormuras commented Feb 1, 2019

Output generated...

ExtensionAlpha.beforeEach()
ExtensionOmega.beforeEach()
  @BeforeEach TestClass.setUpA()
  @BeforeEach TestClass.setUpB()
--> TestClass.test()
  @AfterEach TestClass.tearDownA()
  @AfterEach TestClass.tearDownB()
ExtensionOmega.afterEach()
ExtensionAlpha.afterEach()

Generated by executing:

class WrappingCallbacksAndExtensionsDemo {

	@Nested
	@ExtendWith({ ExtensionAlpha.class, ExtensionOmega.class})
	class TestClass {

		@BeforeEach
		void setUpA() {
			System.out.println("  @BeforeEach " + getClass().getSimpleName() + ".setUpA()");
		}

		@AfterEach
		void tearDownA() {
			System.out.println("  @AfterEach " + getClass().getSimpleName() + ".tearDownA()");
		}

		@BeforeEach
		void setUpB() {
			System.out.println("  @BeforeEach " + getClass().getSimpleName() + ".setUpB()");
		}

		@AfterEach
		void tearDownB() {
			System.out.println("  @AfterEach " + getClass().getSimpleName() + ".tearDownB()");
		}

		@Test
		void test() {
			System.out.println("--> " + getClass().getSimpleName() + ".test()");
		}
	}

	static class ExtensionAlpha implements BeforeEachCallback, AfterEachCallback {

		public void beforeEach(ExtensionContext context) {
			System.out.println(getClass().getSimpleName() + ".beforeEach()");
		}

		public void afterEach(ExtensionContext context) {
			System.out.println(getClass().getSimpleName() + ".afterEach()");
		}
	}

	static class ExtensionOmega implements BeforeEachCallback, AfterEachCallback {

		public void beforeEach(ExtensionContext context) {
			System.out.println(getClass().getSimpleName() + ".beforeEach()");
		}

		public void afterEach(ExtensionContext context) {
			System.out.println(getClass().getSimpleName() + ".afterEach()");
		}
	}

}

Before I start creating a diagram, is this what we want to document? Anything missing?

  • Extension wrap test(): Alpha - Omega - test() - Omega - Alpha
  • @Before/@After callbacks aren't executed in a particular order.

@marcphilipp
Copy link
Member

I think we should add a super class with lifecycle callbacks.

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

I think we should add a super class with lifecycle callbacks.

I agree.

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

ExtensionAlpha.beforeEach()
ExtensionOmega.beforeEach()
  @BeforeEach TestClass.setUpA()
  @BeforeEach TestClass.setUpB()
--> TestClass.test()
  @AfterEach TestClass.tearDownA()
  @AfterEach TestClass.tearDownB()
ExtensionOmega.afterEach()
ExtensionAlpha.afterEach()

If that's the actual output, then that demonstrates that we have a potential 🐛 in the support for user-supplied lifecycle callbacks.

TestClass.tearDownB() should ideally be called before TestClass.tearDownA().

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

In terms of naming, I would suggest we not use "alpha" and "omega", since not all cultures are familiar with those Greek letters.

Instead, I would simply go with "1" and "2" for simplicity and universal clarity.

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

We do actually have proper wrapping support for user-supplied lifecycle callbacks within class hierarchies.

For example, the following...

	@ExtendWith({ ExtensionAlpha.class, ExtensionOmega.class })
	static class TestClassA {

		@BeforeEach
		void setUpA() {
			System.out.println("  @BeforeEach " + getClass().getSimpleName() + ".setUpA()");
		}

		@AfterEach
		void tearDownA() {
			System.out.println("  @AfterEach " + getClass().getSimpleName() + ".tearDownA()");
		}
	}

	static class TestClassB extends TestClassA {

		@BeforeEach
		void setUpB() {
			System.out.println("  @BeforeEach " + getClass().getSimpleName() + ".setUpB()");
		}

		@AfterEach
		void tearDownB() {
			System.out.println("  @AfterEach " + getClass().getSimpleName() + ".tearDownB()");
		}

		@Test
		void testB() {
			System.out.println("--> " + getClass().getSimpleName() + ".testB()");
		}
	}

... results in the following output:

ExtensionAlpha.beforeEach()
ExtensionOmega.beforeEach()
  @BeforeEach TestClassB.setUpA()
  @BeforeEach TestClassB.setUpB()
--> TestClassB.testB()
  @AfterEach TestClassB.tearDownB()
  @AfterEach TestClassB.tearDownA()
ExtensionOmega.afterEach()
ExtensionAlpha.afterEach()

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

Though... we should probably not use the "current class" for such purposes: TestClassB.setUpA() might be confusing for some people.

Thus, it's probably better to use the declaring class/interface for such purposes -- for example, TestClassA.setUpA().

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

Or maybe it's better to just omit the simple class name. 🤔

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

If that's the actual output, then that demonstrates that we have a potential 🐛 in the support for user-supplied lifecycle callbacks.

Coming back to that... I now recall the team decision for that behavior.

We guarantee wrapping behavior within class hierarchies for user-supplied lifecycle callbacks (e.g., @BeforeEach and @AfterEach), but we do not guarantee the order in which multiple lifecycle methods are invoked within a single test class.

To the casual reader, it would appear that we invoke such methods in alphabetical order. However, that is not precisely true. So we should document that ordering analogous to the existing documentation for @Test methods:

By default, test methods will be ordered using an algorithm that is deterministic but intentionally nonobvious. This ensures that subsequent runs of a test suite execute test methods in the same order, thereby allowing for repeatable builds. ...

In addition, and more importantly, we need to document that we do not support wrapping behavior for multiple lifecycle methods declared within a single test class or test interface.

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

Or.... we could fix this.

For example, given the following...

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;

class WrappingCallbacksAndExtensionsDemo {

	@ExtendWith({ Extension1.class, Extension2.class })
	static class TestClassA {

		private final String className = TestClassA.class.getSimpleName();

		@BeforeEach
		void setUpA1(TestInfo testInfo) {
			System.out.println("  @BeforeEach " + className + ".setUpA1()");
		}

		@AfterEach
		void tearDownA1() {
			System.out.println("  @AfterEach " + className + ".tearDownA1()");
		}

		@BeforeEach
		void setUpA2(TestInfo testInfo) {
			System.out.println("  @BeforeEach " + className + ".setUpA2()");
		}

		@AfterEach
		void tearDownA2() {
			System.out.println("  @AfterEach " + className + ".tearDownA2()");
		}

	}

	static class TestClassB extends TestClassA {

		private final String className = TestClassB.class.getSimpleName();

		@BeforeEach
		void setUpB1(TestInfo testInfo) {
			System.out.println("  @BeforeEach " + className + ".setUpB1()");
		}

		@AfterEach
		void tearDownB1() {
			System.out.println("  @AfterEach " + className + ".tearDownB1()");
		}

		@BeforeEach
		void setUpB2(TestInfo testInfo) {
			System.out.println("  @BeforeEach " + className + ".setUpB2()");
		}

		@AfterEach
		void tearDownB2() {
			System.out.println("  @AfterEach " + className + ".tearDownB2()");
		}

		@BeforeEach
		void setUpB3(TestInfo testInfo) {
			System.out.println("  @BeforeEach " + className + ".setUpB3()");
		}

		@AfterEach
		void tearDownB3() {
			System.out.println("  @AfterEach " + className + ".tearDownB3()");
		}

		@Test
		void testB() {
			System.out.println("--> " + className + ".testB()");
		}

	}

	static class TestClassC extends TestClassB {

		private final String className = TestClassC.class.getSimpleName();

		@BeforeEach
		void setUpC1(TestInfo testInfo) {
			System.out.println("  @BeforeEach " + className + ".setUpC1()");
		}

		@AfterEach
		void tearDownC1() {
			System.out.println("  @AfterEach " + className + ".tearDownC1()");
		}

		@BeforeEach
		void setUpC2(TestInfo testInfo) {
			System.out.println("  @BeforeEach " + className + ".setUpC2()");
		}

		@AfterEach
		void tearDownC2() {
			System.out.println("  @AfterEach " + className + ".tearDownC2()");
		}

		@BeforeEach
		void setUpC3(TestInfo testInfo) {
			System.out.println("  @BeforeEach " + className + ".setUpC3()");
		}

		@AfterEach
		void tearDownC3() {
			System.out.println("  @AfterEach " + className + ".tearDownC3()");
		}

	}

	static class Extension1 implements BeforeEachCallback, AfterEachCallback {

		@Override
		public void beforeEach(ExtensionContext context) {
			System.out.println(getClass().getSimpleName() + ".beforeEach()");
		}

		@Override
		public void afterEach(ExtensionContext context) {
			System.out.println(getClass().getSimpleName() + ".afterEach()");
		}
	}

	static class Extension2 implements BeforeEachCallback, AfterEachCallback {

		@Override
		public void beforeEach(ExtensionContext context) {
			System.out.println(getClass().getSimpleName() + ".beforeEach()");
		}

		@Override
		public void afterEach(ExtensionContext context) {
			System.out.println(getClass().getSimpleName() + ".afterEach()");
		}
	}

}

... with a minor change I've made to org.junit.jupiter.engine.descriptor.ClassTestDescriptor.registerAfterEachMethodAdapters(ExtensionRegistry) locally on my machine, I now get the following output when executing the above TestClassC test class:

Extension1.beforeEach()
Extension2.beforeEach()
  @BeforeEach TestClassA.setUpA1()
  @BeforeEach TestClassA.setUpA2()
  @BeforeEach TestClassB.setUpB1()
  @BeforeEach TestClassB.setUpB2()
  @BeforeEach TestClassB.setUpB3()
  @BeforeEach TestClassC.setUpC1()
  @BeforeEach TestClassC.setUpC2()
  @BeforeEach TestClassC.setUpC3()
--> TestClassB.testB()
  @AfterEach TestClassC.tearDownC3()
  @AfterEach TestClassC.tearDownC2()
  @AfterEach TestClassC.tearDownC1()
  @AfterEach TestClassB.tearDownB3()
  @AfterEach TestClassB.tearDownB2()
  @AfterEach TestClassB.tearDownB1()
  @AfterEach TestClassA.tearDownA2()
  @AfterEach TestClassA.tearDownA1()
Extension2.afterEach()
Extension1.afterEach()

@junit-team/junit-lambda, Thoughts?

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

My local fix works with interface default methods as well.

Declaring:

class TestClassC extends TestClassB implements TestInterfaceX {
    // ...
}

... with the following test interface

interface TestInterfaceX {

	String className = TestInterfaceX.class.getSimpleName();

	@BeforeEach
	default void setUpX1(TestInfo testInfo) {
		System.out.println("  @BeforeEach " + className + ".setUpX1()");
	}

	@AfterEach
	default void tearDownX1() {
		System.out.println("  @AfterEach " + className + ".tearDownX1()");
	}

	@BeforeEach
	default void setUpX2(TestInfo testInfo) {
		System.out.println("  @BeforeEach " + className + ".setUpX2()");
	}

	@AfterEach
	default void tearDownX2() {
		System.out.println("  @AfterEach " + className + ".tearDownC2()");
	}

}

... yields the following output:

Extension1.beforeEach()
Extension2.beforeEach()
  @BeforeEach TestClassA.setUpA1()
  @BeforeEach TestClassA.setUpA2()
  @BeforeEach TestClassB.setUpB1()
  @BeforeEach TestClassB.setUpB2()
  @BeforeEach TestClassB.setUpB3()
  @BeforeEach TestInterfaceX.setUpX1()
  @BeforeEach TestInterfaceX.setUpX2()
  @BeforeEach TestClassC.setUpC1()
  @BeforeEach TestClassC.setUpC2()
  @BeforeEach TestClassC.setUpC3()
--> TestClassB.testB()
  @AfterEach TestClassC.tearDownC3()
  @AfterEach TestClassC.tearDownC2()
  @AfterEach TestClassC.tearDownC1()
  @AfterEach TestInterfaceX.tearDownC2()
  @AfterEach TestInterfaceX.tearDownX1()
  @AfterEach TestClassB.tearDownB3()
  @AfterEach TestClassB.tearDownB2()
  @AfterEach TestClassB.tearDownB1()
  @AfterEach TestClassA.tearDownA2()
  @AfterEach TestClassA.tearDownA1()
Extension2.afterEach()
Extension1.afterEach()

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

Example in favor of my "final point" above:

	@ExtendWith({ Extension1.class, Extension2.class })
	static class TestClassZ {

		private final String className = TestClassB.class.getSimpleName();

		@BeforeEach
		void startDatabase(TestInfo testInfo) {
			System.out.println("  @BeforeEach " + className + ".startDatabase()");
		}

		@AfterEach
		void closeDatabase() {
			System.out.println("  @AfterEach " + className + ".closeDatabase()");
		}

		@BeforeEach
		void setUpZ2(TestInfo testInfo) {
			System.out.println("  @BeforeEach " + className + ".setUpZ2()");
		}

		@AfterEach
		void tearDownZ2() {
			System.out.println("  @AfterEach " + className + ".tearDownZ2()");
		}

		@BeforeEach
		void setUpZ3(TestInfo testInfo) {
			System.out.println("  @BeforeEach " + className + ".setUpZ3()");
		}

		@AfterEach
		void tearDownZ3() {
			System.out.println("  @AfterEach " + className + ".tearDownZ3()");
		}

		@Test
		void testZ() {
			System.out.println("--> " + className + ".testZ()");
		}

	}

Executing TestClassZ with the "wrapping fix results in:

Extension1.beforeEach()
Extension2.beforeEach()
  @BeforeEach TestClassB.startDatabase()
  @BeforeEach TestClassB.setUpZ2()
  @BeforeEach TestClassB.setUpZ3()
--> TestClassB.testZ()
  @AfterEach TestClassB.closeDatabase()
  @AfterEach TestClassB.tearDownZ3()
  @AfterEach TestClassB.tearDownZ2()
Extension2.afterEach()
Extension1.afterEach()

And that demonstrates that there is in fact zero correlation between methods like startDatabase() and closeDatabase().

Long story, short: let's just document the status quo for execution order for user-supplied lifecycle methods. 😉

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

I'm pasting the "fix" I experimented with locally here for future reference in case it proves useful.

Change in ClassTestDescriptor:

/**
 * {@link Comparator} that reverses the order of methods declared within the
 * same class.
 */
Comparator<Method> lifecycleMethodComparator = (Method m1, Method m2) -> {
	Class<?> class1 = m1.getDeclaringClass();
	Class<?> class2 = m2.getDeclaringClass();

	if (class1.equals(class2)) {
		return -1;
	}

	return 0;
};

private void registerAfterEachMethodAdapters(ExtensionRegistry registry) {
	// Make a local copy since findAfterEachMethods() returns an immutable list.
	List<Method> afterEachMethods = new ArrayList<>(findAfterEachMethods(this.testClass));

	// Since the bottom-up ordering of afterEachMethods will later be reversed when the
	// synthesized AfterEachMethodAdapters are executed within TestMethodTestDescriptor,
	// we have to reverse the afterEachMethods list to put them in top-down order before
	// we register them as synthesized extensions.
	Collections.reverse(afterEachMethods);

	// Reverse the order of @AfterEach methods declared within the same class/interface.
	afterEachMethods.sort(lifecycleMethodComparator);

	registerMethodsAsExtensions(afterEachMethods, registry, this::synthesizeAfterEachMethodAdapter);
}

@marcphilipp
Copy link
Member

Long story, short: let's just document the status quo for execution order for user-supplied lifecycle methods.

👍

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

Update: Deliverables have been overhauled to reflect the current scope.

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

Examples introduced in 591e5c0 demonstrate proper and improper lifecycle method configuration.

Note, however, that that commit only contains the code: it doesn't update the User Guide or Javadoc.

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

Executing BrokenLifecycleMethodConfigDemo results in:

Extension1.beforeEach()
Extension2.beforeEach()
  @BeforeEach BrokenLifecycleMethodConfigDemo.insertTestDataIntoDatabase()
  @BeforeEach BrokenLifecycleMethodConfigDemo.connectToDatabase()
    @Test BrokenLifecycleMethodConfigDemo.testDatabaseFunctionality()
  @AfterEach BrokenLifecycleMethodConfigDemo.disconnectFromDatabase()
  @AfterEach BrokenLifecycleMethodConfigDemo.deleteTestDataInDatabase()
Extension2.afterEach()
Extension1.afterEach()

@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

Executing DatabaseTestsDemo results in:

Extension1.beforeEach()
Extension2.beforeEach()
  @BeforeEach AbstractDatabaseTests.connectToDatabase()
  @BeforeEach DatabaseTestsDemo.insertTestDataIntoDatabase()
    @Test DatabaseTestsDemo.testDatabaseFunctionality()
  @AfterEach DatabaseTestsDemo.deleteTestDataInDatabase()
  @AfterEach AbstractDatabaseTests.disconnectFromDatabase()
Extension2.afterEach()
Extension1.afterEach()

@sbrannen sbrannen self-assigned this Feb 2, 2019
@sbrannen
Copy link
Member

sbrannen commented Feb 2, 2019

@sormuras and @marcphilipp, with regard to the undesirable behavior in BrokenLifecycleMethodConfigDemo, I suggest we state in the document something along the lines of the following.

Due to the aforementioned behavior, the JUnit Team recommends that developers declare at most one of each type of lifecycle method (i.e., @BeforeAll, @AfterAll, @BeforeEach, @AfterEach) per test class or test interface.

sormuras added a commit that referenced this issue Feb 5, 2019
sormuras added a commit that referenced this issue Feb 5, 2019
Copy and paste them to https://app.zenuml.com to generate PNGs.

Issue #1620
sbrannen added a commit that referenced this issue Feb 5, 2019
This commit documents static invocations of @BeforeAll and @afterall
methods.

Issue: #1620
sbrannen added a commit that referenced this issue Feb 6, 2019
This commit introduces Javadoc for wrapping semantics for
@BeforeAll/@afterall methods declared in interfaces.

Issue: #1620
marcphilipp added a commit that referenced this issue Feb 6, 2019
marcphilipp added a commit that referenced this issue Feb 7, 2019
@ghost ghost removed the status: in progress label Feb 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants