Junit5: Migration to JUnit 5: Correct configuration about static nested classes

Created on 5 Feb 2018  路  24Comments  路  Source: junit-team/junit5

I have the following code for:

  • Spring Framework 4.3.x
  • JUnit 4.x

The following works fine (the experience is already shared in: Testing one Spring Test Class for many different set of profiles)

Alpha

@Transactional
@RunWith(Parameterized.class)
@ContextConfiguration(classes={RootApplicationContext.class})
@TestExecutionListeners(listeners={LoggingTestExecutionListener.class}, mergeMode=MergeMode.MERGE_WITH_DEFAULTS)
public abstract class PersonaServiceImplTest {

    private static final Logger logger = LoggerFactory.getLogger(PersonaServiceImplTest.class.getSimpleName());

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Autowired
    private Environment environment;

    @Autowired
    private PersonaService personaServiceImpl;

    private final Persona persona;

    @Parameters
    public static Collection<Persona[]> data() {
        return Arrays.asList(new Persona[][] {
            {PersonaFactory.crearPersona01()},
            {PersonaFactory.crearPersona02()},
            {PersonaFactory.crearPersona03()},
            {PersonaFactory.crearPersona04()},
            {PersonaFactory.crearPersonaMix()}
        });
    }

    public PersonaServiceImplTest(Persona persona){
        this.persona = persona;
    }

    @Before
    public void setup(){
        logger.info("Profiles: {}", Arrays.toString(environment.getActiveProfiles()));
    }

    public void saveOneTest(){
                ... the asserts
    }

    public void findOneTest(){
                ... the asserts
    }

       ...

    @ActiveProfiles(resolver=TestJdbcActiveProfilesResolver.class)
    public static class ForJdbc extends PersonaServiceImplTest {

        public ForJdbc(Persona persona){
            super(persona);
        }

        @Test
        @Override
        @Sql(scripts={"classpath:/com/manuel/jordan/db/delete.sql"})
        public void saveOneTest(){
            super.saveOneTest();
        }

        @Test
        @Override
        public void findOneTest(){
            super.findOneTest();
        }

                ...

       }

      @ActiveProfiles(resolver=TestHibernateActiveProfilesResolver.class)
      public static class ForHibernate extends PersonaServiceImplTest {

        public ForHibernate(Persona persona){
            super(persona);
        }

        @Test
        @Override
        @Sql(scripts={"classpath:/com/manuel/jordan/db/delete.sql"})
        public void saveOneTest(){
            super.saveOneTest();
        }

        @Test
        @Override
        public void findOneTest(){
            super.findOneTest();
        }

               ...

       }

}

Thus practically through a _static nested class_ (mandatory be public) with @ActiveProfiles I get the desired behaviour explained in the SO post. Until here I am fine.

I like this approach because it is centralized to one class and its static nested classes.

I did do a migration to JUnit 5 (same for Spring Framework to 5). Therefore I have the following now:

Beta

@Transactional
@SpringJUnitConfig(classes={RootApplicationContext.class})
@TestExecutionListeners(listeners={LoggingTestExecutionListener.class}, mergeMode=MergeMode.MERGE_WITH_DEFAULTS)
public abstract class PersonaServiceImplTest {

    private static final Logger logger = LoggerFactory.getLogger(PersonaServiceImplTest.class.getSimpleName());

    @Autowired
    private Environment environment;

    @Autowired
    private PersonaService personaServiceImpl;

    //private final Persona persona;

    static Stream<Persona> data(){
        return Stream.of(
                PersonaFactory.crearPersona01(),
                PersonaFactory.crearPersona02(),
                PersonaFactory.crearPersona03(),
                PersonaFactory.crearPersona04(),
                PersonaFactory.crearPersonaMix());
    }

//  public PersonaServiceImplTest(Persona persona){
//      this.persona = persona;
//  }

    @BeforeEach
    public void setup(){
        logger.info("Profiles: {}", Arrays.toString(environment.getActiveProfiles()));
    }

    public void saveOneTest(Persona persona){
                ... the asserts
    }

    public void findOneTest(Persona persona){
                ... the asserts
    }

    ...

    /**
     *
     * @author manueljordan
     * @since web-29
     */
    @ActiveProfiles(resolver=TestJdbcActiveProfilesResolver.class)
    public static class ForJdbc extends PersonaServiceImplTest {

//      public ForJdbc(Persona persona){
//          super(persona);
//      }

        @Override
        @ParameterizedTest
        @MethodSource("data")
        @Sql(scripts={"classpath:/com/manuel/jordan/db/delete.sql"})
        public void saveOneTest(Persona persona){
            super.saveOneTest(persona);
        }

        @Override
        @ParameterizedTest
        @MethodSource("data")
        public void findOneTest(Persona persona){
            super.findOneTest(persona);
        }

                ...

    }

    /**
     *
     * @author manueljordan
     * @since web-29
     */
    @ActiveProfiles(resolver=TestHibernateActiveProfilesResolver.class)
    public static class ForHibernate extends PersonaServiceImplTest {

//      public ForHibernate(Persona persona){
//          super(persona);
//      }

        @Override
        @ParameterizedTest
        @MethodSource("data")
        @Sql(scripts={"classpath:/com/manuel/jordan/db/delete.sql"})
        public void saveOneTest(Persona persona){
            super.saveOneTest(persona);
        }

        @Override
        @ParameterizedTest
        @MethodSource("data")
        public void findOneTest(Persona persona){
            super.findOneTest(persona);
        }

                ...

    }
}

The code shown compiles fine but when it is executed none test is executed, practically nothing.

Thus two questions:

What is missing? (Some extra special configuration or some new annotation to used) or Is it now the expected behavior?

I have tried other combinations or ways, but has no sense to share because they are not work and has no sense do verbose this post.

My unique way to get the desired behaviour in JUnit 5, _about the Spring Profile_, is have three _outer_ classes:

@Transactional
@SpringJUnitConfig(classes={RootApplicationContext.class})
@TestExecutionListeners(listeners={LoggingTestExecutionListener.class}, mergeMode=MergeMode.MERGE_WITH_DEFAULTS)
abstract class AbstractPersonaServiceImplTest {

...

}

@ActiveProfiles(resolver=TestHibernateActiveProfilesResolver.class)
class PersonaServiceImplForHibernateTest extends AbstractPersonaServiceImplTest {

...

}

@ActiveProfiles(resolver=TestJdbcActiveProfilesResolver.class)
class PersonaServiceImplForJdbcTest extends AbstractPersonaServiceImplTest {

...

}

Just curious if is possible have one outer class with nested static class.

Thank you

works-as-designed

All 24 comments

Just curious if is possible have one outer class with nested static class.

That should not be a problem with JUnit Jupiter.

Static nested classes are equivalent to top-level classes in that each such test class can be executed independently.

With the example you have provided you should therefore be able to execute all static nested classes in your Beta section separately (in the IDE) or simultaneously if you select the containing package.

Ignoring everything from Spring (which does not influence how tests are discovered or executed within JUnit Jupiter), I have reduced your Beta example to the following:

import java.util.stream.Stream;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

abstract class PersonaServiceImplTest {

    static Stream<String> data() {
        return Stream.of("foo", "bar");
    }

    @BeforeEach
    void setup(TestInfo testInfo) {
        System.err.println("@BeforeEach: " + testInfo.getTestMethod());
    }

    void saveOneTest(String s) {
    }

    void findOneTest(String s) {
    }

    static class ForJdbc extends PersonaServiceImplTest {

        @ParameterizedTest
        @MethodSource("data")
        @Override
        void saveOneTest(String s) {
            super.saveOneTest(s);
        }

        @ParameterizedTest
        @MethodSource("data")
        @Override
        void findOneTest(String s) {
            super.findOneTest(s);
        }
    }

    static class ForHibernate extends PersonaServiceImplTest {

        @ParameterizedTest
        @MethodSource("data")
        @Override
        void saveOneTest(String s) {
            super.saveOneTest(s);
        }

        @ParameterizedTest
        @MethodSource("data")
        @Override
        void findOneTest(String s) {
            super.findOneTest(s);
        }

    }
}

And that outputs the following as expected:

@BeforeEach: Optional[void org.junit.PersonaServiceImplTest$ForHibernate.saveOneTest(java.lang.String)]
@BeforeEach: Optional[void org.junit.PersonaServiceImplTest$ForHibernate.saveOneTest(java.lang.String)]
@BeforeEach: Optional[void org.junit.PersonaServiceImplTest$ForHibernate.findOneTest(java.lang.String)]
@BeforeEach: Optional[void org.junit.PersonaServiceImplTest$ForHibernate.findOneTest(java.lang.String)]
@BeforeEach: Optional[void org.junit.PersonaServiceImplTest$ForJdbc.saveOneTest(java.lang.String)]
@BeforeEach: Optional[void org.junit.PersonaServiceImplTest$ForJdbc.saveOneTest(java.lang.String)]
@BeforeEach: Optional[void org.junit.PersonaServiceImplTest$ForJdbc.findOneTest(java.lang.String)]
@BeforeEach: Optional[void org.junit.PersonaServiceImplTest$ForJdbc.findOneTest(java.lang.String)]

I am therefore closing this issue as "works as designed".

For future reference please post the simplest version of your tests and exclude the use of third-party frameworks (like Spring) whenever possible: that will make things easier for everyone involved.

Thanks by the reply.

But how I have reported I have confirmed that Beta never is executed. I mean, the two static nested classes never were executed.

I have seen your new comment, I will try your code version. I want confirm this. Thank you

Well, the output I included proves that the static nested classes are in fact executed (verified in Eclipse 4.7.2).

How are you trying to execute them?

I execute through Jenkins through a Gradle command for a multimodule project, in this case the command is: gradle :junit-01-service-impl:junitPlatformTest --parallel.

The same command shown above works fine for the three outer classes, but for the outer with the two static nested classes nothing happens

It may have to do with the names of the classes. Are you using the default includeClassNamePattern (^.*Tests?$)?

Interesting, I have tried:

abstract class SamPersonaServiceImplTest {

    static Stream<String> data() {
        return Stream.of("foo", "bar");
    }

    @BeforeEach
    void setup(TestInfo testInfo) {
        System.err.println("@BeforeEach: " + testInfo.getTestMethod());
    }

    void saveOneTest(String s) {
    }

    void findOneTest(String s) {
    }

    static class ForJdbc extends SamPersonaServiceImplTest {

        @ParameterizedTest
        @MethodSource("data")
        @Override
        void saveOneTest(String s) {
            super.saveOneTest(s);
        }

        @ParameterizedTest
        @MethodSource("data")
        @Override
        void findOneTest(String s) {
            super.findOneTest(s);
        }
    }

    static class ForHibernate extends SamPersonaServiceImplTest {

        @ParameterizedTest
        @MethodSource("data")
        @Override
        void saveOneTest(String s) {
            super.saveOneTest(s);
        }

        @ParameterizedTest
        @MethodSource("data")
        @Override
        void findOneTest(String s) {
            super.findOneTest(s);
        }

    }

}

Nothing happens ...

Correct @marcphilipp now works if I use: ForJdbcTest and ForHibernateTest (Test pattern used in the final of each name). Why the sample code of Sam did work and not mime? Something special does the Gradle plugin? ..... It works without any problem in JUnit 4 (about not use Test pattern)

Sam used Eclipse to execute them explicitly. You used our Gradle plugin which does classpath scanning with a pattern that did not include them.

The same thing would happen if you name a top-level class Foo: Eclipse would run it but our classpath scanning wouldn't find it.

Thanks by your explanation and your time to both. Sorry to bother you but I thought it could be a potential bug. Just all, you know, if it worked in JUnit 4, why not in 5. Thanks by your valuable and friendly support.

Could you share please the link about the Gradle situation to generate the Test Report? We talk about this in other post. I want track that "request" for that feature. Thanks.

Currently there's a community-written extra Gradle plugin to generate a report:
https://github.com/kncept/junit-reporter

However, native support for Gradle (including the ability to generate a report) is being worked on at the moment by the Gradle team and is currently slated for Gradle 4.6.

Thanks by the quick reply and information.

However, native support for Gradle (including the ability to generate a report) is being worked on at the moment by the Gradle team and is currently slated for Gradle 4.6.

Yes, I want to know if exists a GitHub request about the native support. I want keep a track for that. Is there a link for that? or is handled internally?

Thanks a lot!

Hello @sbrannen @marcphilipp

Sorry to bother you, just for your consideration

Let Gradle Report shows the report generated through the name attribute of the @ParameterizedTest #4712

I know it is not a bug about JUnit itself, but since the current Console Launcher documentation works around stand alone mode. I think valuable for the developer get that data into some kind of report, in this case through Gradle

I'm not quite sure what you are requesting.

If you are repeating what you've already requested in https://github.com/gradle/gradle/issues/4712, that will be handled by the Gradle team.

If you are requesting some action from the JUnit team, please open a new issue since _this_ issue has already been resolved.

I just share the link to you both. Because _I was not sure_ if the problem or situation was really about JUnit because I can see the data through Jenkins and not through Gradle. That's why the post has been created in Gradle and not in a new post in JUnit. Thus I've assumed through your own experiments/testing would confirm the same experience by your side. That's all

I just share the link to you both.

It's totally fine to share links with us to raise our awareness! We certainly welcome that. 馃槈

But... it's just not a good practice to post stuff like that in an issue that's already been closed, since it might get overlooked. Plus, it's also not a good idea to mix topics in a single issue in an issue tracker.

Regarding the status quo for Gradle's reporting for the JUnit Platform, yes, we are aware that it is a work in progress, and the Gradle team is aware of that as well. It will be addressed in time with the main hindrance being the fact that there is currently no standardized reporting format for the JUnit Platform.

But... it's just not a good practice to post stuff like that in an issue that's already been closed, since it might get overlooked. Plus, it's also not a good idea to mix topics in a single issue in an issue tracker.

I understand, but because I was not sure that JUnit would be the main reason of this situation, I decided to not create a new post to avoid bother you without a solid reason. I mean if the problem was really about Gradle has no sense report this to JUnit. Thus I only have shared the link.

Regarding the status quo for Gradle's reporting for the JUnit Platform, yes, we are aware that it is a work in progress, and the Gradle team is aware of that as well. It will be addressed in time with the main hindrance being the fact that there is currently no standardized reporting format for the JUnit Platform.

I see, I didn't know if you are closely checking the problems about Gradle with Junit, thus I have decided to share the link.

There's an in:testing label for Gradle that you can _watch_ for known issues:

https://github.com/gradle/gradle/issues?q=is%3Aissue+is%3Aopen+label%3Ain%3Atesting

Also, the issue you raised is basically a duplicate of this one:

https://github.com/gradle/gradle/issues/4423

Thanks by the tag information

Was this page helpful?
0 / 5 - 0 ratings