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

Parameterized BeforeEach or AfterEach only #3157

Closed
sbernard31 opened this issue Feb 16, 2023 · 6 comments
Closed

Parameterized BeforeEach or AfterEach only #3157

sbernard31 opened this issue Feb 16, 2023 · 6 comments

Comments

@sbernard31
Copy link

sbernard31 commented Feb 16, 2023

My need :

I have a class containing several tests.
I have a @BeforeEach and @AfterEach which setup and tear down each tests.

Now I would like to parameterize test but I don't want to get param on test method but only on @BeforeEach method.

Here is a very simplified example of code of what I would like to do :
(In my real use case, my start() method is far more complicated with more than one object to initialize and more than 1 argument as parameter)

public class TestSuite {
     
    private MyObjectToTest objectToTest;
     
    @ParameterizedForEach(name = "My custom name {}")
    @ValueSource(strings = { "param1", "param2", "param3"})
    @BeforeEach
    public void start(String param) {
        objectToTest = new MyObjectToTest(param);
    }

    @AfterEach
    public void stop() throws InterruptedException {
        objectToTest.destroy();
    }

    @Test
    public void test1() {
        assertThat(objectToTest).isValid();
    }

    @Test
    public void test2() {
        assertThat(objectToTest).isOK();
    }

My solution for now :

I used the CustomParameterResolver way described at

but this does not reallly elegant as I need to add argument to test method too.
This looks like :

@ExtendWith(CustomParameterResolver.class)
public class TestSuite {

    @ParameterizedTest(name = "My custom name {}")
    @ValueSource(strings = { "param1", "param2", "param3"})
    @Retention(RetentionPolicy.RUNTIME)
    private @interface TestAllParams {
    }

    private MyObjectToTest objectToTest;
     
    @BeforeEach
    public void start(String param) {
        objectToTest = new MyObjectToTest(param);
    }

    @AfterEach
    public void stop() throws InterruptedException {
        objectToTest.destroy();
    }

    @TestAllParams
    public void test1(String param)  {
        assertThat(objectToTest).isValid();
    }

    @TestAllParams
    public void test2(String param)  {
        assertThat(objectToTest).isOK();
    }

Question ?

Do you think this feature request make sense ?
Any advice to achieve this in a better / more elegant way ?

@marcphilipp
Copy link
Member

It looks like what you're after is class-level parameterization that is tracked in #878.

@sbernard31
Copy link
Author

sbernard31 commented Feb 17, 2023

@marcphilipp, 🙏 thx for reply.

I tried to read the whole issue.
If I well understand class-level parameterization does not exist yet ? right ?

The proposed design would be #878 (comment).

So in my case it would looks like :

@ParameterizedContainer(name = ...)
@ValueSource(strings = { "param1", "param2", "param3"})
public class TestSuite {
    @Parameter(0)
    private String param;     

    private MyObjectToTest objectToTest;
     
    @BeforeEach
    public void start() {
        objectToTest = new MyObjectToTest(param);
    }

    @AfterEach
    public void stop() throws InterruptedException {
        objectToTest.destroy();
    }

    @Test
    public void test1() {
        assertThat(objectToTest).isValid();
    }

    @Test
    public void test2() {
        assertThat(objectToTest).isOK();
    }

It should fit my needs. 👍

Ideally instead of having parameter as attribute I would prefer to get it as argument like this ✨ :

@ParameterizedContainer(name = ...)
@ValueSource(strings = { "param1", "param2", "param3"})
public class TestSuite {
     
    @BeforeEach
    @Parameterized
    public void start(String param) {
        objectToTest = new MyObjectToTest(param);
    }
    
    ... ...

OR

@ParameterizedContainer(name = ...)
@ValueSource(strings = { "param1", "param2", "param3"})
public class TestSuite {
     
    @BeforeEach
    public void start(@Parameter(0) String param) {
        objectToTest = new MyObjectToTest(param);
    }
    
    ... ...

Sorry for the boring question but it is plan at short/mid term ?
Do you know if someone is working on this currently ?
Do you think this is something achievable for a new contributor ?

@sbernard31
Copy link
Author

sbernard31 commented Feb 17, 2023

Reading #878, I find other work around, so I decide to list it here.

1) using CustomParameterResolver as explain at https://stackoverflow.com/a/69265907/5088764
(Need to add CustomParameterResolver and MappedParameterContext)

@ExtendWith(CustomParameterResolver.class)
public class TestSuite {

    @ParameterizedTest(name = "My custom name {}")
    @ValueSource(strings = { "param1", "param2", "param3"})
    @Retention(RetentionPolicy.RUNTIME)
    private @interface TestAllParams {
    }

    private MyObjectToTest objectToTest;
     
    @BeforeEach
    public void start(String param) {
        objectToTest = new MyObjectToTest(param);
    }

    @AfterEach
    public void stop() throws InterruptedException {
        objectToTest.destroy();
    }

    @TestAllParams
    public void test1(String param)  {
        assertThat(objectToTest).isValid();
    }

    @TestAllParams
    public void test2(String param)  {
        assertThat(objectToTest).isOK();
    }

2) using TestFactory as explain at #878 (comment)
(no to add TestingUtils class)

public class TestSuite {
    @TestFactory
    Stream<DynamicNode> runTests() throws Exception {
        return TestingUtils.parameterizedClassTester("param={0}", Tests.class,
               Stream.of(Arguments.of("param1"), Arguments.of("param2"), Arguments.of("param3")));
    }

    static class Tests {

        private String param;     

        public TestSuite(String param){
            this.param = param;
        }

        private MyObjectToTest objectToTest;
     
        @BeforeEach
        public void start() {
            objectToTest = new MyObjectToTest(param);
        }

        @AfterEach
        public void stop() throws InterruptedException {
            objectToTest.destroy();
        }

        @Test
        public void test1() {
            assertThat(objectToTest).isValid();
        }

        @Test
        public void test2() {
            assertThat(objectToTest).isOK();
        }

3) using abstract/interface + concrete class as explain at #871 (comment)
(no need extra class)

public abstract class TestSuite {

   public TestSuite1 extends TestSuite {
       TestSuite1() {
           super("param1")
       }
   }

   public TestSuite2 extends TestSuite {
       TestSuite2() {
           super("param2")
       }
   }

   public TestSuite3 extends TestSuite {
       TestSuite3() {
           super("param3")
       }
   }

    public TestSuite(String param){
        this.param = param;
    }
    private String param;     

    private MyObjectToTest objectToTest;
     
    @BeforeEach
    public void start() {
        objectToTest = new MyObjectToTest(param);
    }

    @AfterEach
    public void stop() throws InterruptedException {
        objectToTest.destroy();
    }


    @Test
    public void test1() {
        assertThat(objectToTest).isValid();
    }

    @Test
    public void test2() {
        assertThat(objectToTest).isOK();
    }

All of those workarounds have drawback but it could help waiting for class-level parameterization

@stale
Copy link

stale bot commented Apr 11, 2023

If you would like us to be able to process this issue, please provide the requested information. If the information is not provided within the next 3 weeks, we will be unable to proceed and this issue will be closed.

@sbernard31
Copy link
Author

please provide the requested information.

Maybe I totally missed something but I'm not sure what are requested information ?

@marcphilipp
Copy link
Member

Maybe I totally missed something but I'm not sure what are requested information ?

Never mind, we should have removed the "waiting-for-feedback" label after your previous comment.

Team decision: We'll take your feedback into consideration in the context of #878.

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

3 participants