2

I'm trying to write a test class that requires the use of a certain setup. When there's only 1 setup, this is easy with @BeforeEach:

@BeforeEach public void setup() {
  // my setup code
}

@Test public void test1() {
  // ...
}

@Test public void test2() {
  // ...
}

@Test public void test3() {
  // ...
}

But what can I do when there are several setups to choose from? Of course, I could forget the @BeforeEach altogether and ask colleagues to call the setup method they'd like to use:

@Test public void test1() {
  setupA();
  // ...
}

@Test public void test2() {
  setupB();
  // ...
}

@Test public void test3() {
  setupB();
  // ...
}

But this no longer forces the use of one of my setup methods. Is there a way to implement a "parametrized @BeforeEach"? Something like (made-up syntax):

enum SetupType {A, B, C};

@BeforeEach public void setup(SetupType setupType) {
  switch (setupType) {
  case A:
    setupA();
    break;
  case B:
    setupB();
    break;
  case C:
    setupC();
    break;
  default:
    fail("Unrecognized setup.");
}

@Test
@BeforeEachParameter(SetupType.A)
public void test1() {
  // ...
}

@Test
@BeforeEachParameter(SetupType.B)
public void test2() {
  // ...
}

@Test
@BeforeEachParameter(SetupType.B)
public void test3() {
  // ...
}

Or even better, baking it into the @Test annotation?

@TestWithSetupA public void test1() {
  // ...
}

@TestWithSetupB public void test2() {
public void test2() {
  // ...
}

@TestWithSetupB public void test3() {
public void test3() {
  // ...
}
Andrew Cheong
  • 29,362
  • 15
  • 90
  • 145
  • 2
    Aren't those just different tests which belong in different test classes? – M. Deinum Oct 30 '19 at 08:01
  • @M.Deinum - I considered that, but it seemed a little weird, e.g. suppose I add a some new functionality, and I want to write tests for this functionality in the case of a single user, two users, two users where one user has restricted access, etc. I feel the tests are very closely related and want to be able to compare the variation in assertions without having to look across files. – Andrew Cheong Oct 30 '19 at 08:06
  • 1
    I would personally prefer an explicity call instead of using a @BeforeEach – Ilario Pierbattista Oct 30 '19 at 08:08
  • 1
    Then just do the setup in your test method. Why try to shoehorn that into the testing framework by abusing the `@Before`? Or create an external fixture containing the setup and call that from your test method, that way you can also reuse that in other test classes. – M. Deinum Oct 30 '19 at 08:08
  • @M.Deinum - Well, I suppose I'm trying to find a technical solution to help me with a human problem. Due to demand and necessity, we were forced to allow outside contributions to a system I own, and allow those contributors to be autonomous, because I alone could not keep up with every PR at the pace necessary to continue business. Those contributors lack experience / knowledge in my system and find any way they can to achieve a bare minimum (but often incorrect) setup in order to write their tests. I want to force the BeforeEach so that they cannot get around one of my setups (easily). – Andrew Cheong Oct 30 '19 at 08:15
  • @M.Deinum - In other words, it's not reusability that I'm concerned with. I'm asking if there's a way to enforce essentially "OneOf BeforeEach" in JUnit5. – Andrew Cheong Oct 30 '19 at 08:21
  • By implementing a rather complex technologic solution by abusing something that isn't ment for that. What prevents them from adding a test without setup? Even with your solution this wouldn't prevent them from doing so. Nor will it prevent them from extending it with a new before(each) to do what they want. So although it might look like a solution it is time spend on something that doesn't solve your actual problem. – M. Deinum Oct 30 '19 at 08:22

1 Answers1

3

yes and it's indeed pretty simple. You can use the test annotation Tags and in the before each setup inject the TestInfo object but you will need JUnit 5.

Here's a working JUnit

public class TestBeforeEach {

@BeforeEach
public void setUp(TestInfo testInfo) {
    System.out.println(testInfo.getTags());
}

@Test
@Tag( "setup1" )
public void test1() {

}

@Test
@Tag( "setup2" )
public void test2() {

}

Hope this helps. Cheers!

Sergio Arrighi
  • 530
  • 1
  • 4
  • 15
  • 1
    An @annotation cannot have an Enum parameter, plain string is the only option for `@Tag` (though you use a `public static final` constant to hold it) – drekbour Oct 30 '19 at 21:47
  • 1
    I like this solution but would add that you can create a meta-annotation `@TestWithSetupA` that applies both `@Test` and `@Tag("SetupA")`. See https://junit.org/junit5/docs/current/user-guide/#writing-tests-meta-annotations – drekbour Oct 30 '19 at 21:49