JUnit 5 @MethodSource in nested class
Asked Answered
W

5

52

I am working with JUnit 5 and I want to create parameterized tests in a nested class. For example:

class CardTest {
    @Nested
    class Cost {
        Stream<Arguments> cards() {
            return Stream.of(
                Arguments.of(Card.common(0, Color.RED), 0),
                /** Other Data **/
                Arguments.of(Card.choseColor(), 50)
            );
        }
        @MethodSource("cards")
        @ParameterizedTest
        void cardCost(Card card, int cost) {
            assertThat(card.cost()).isEqualTo(cost);
        }
    }
    /** Some other nested classes or simple methods **/
}

The problem is @MethodSource required that specified method must be static. But Java does not allow static methods in non-static inner classes. If I declare the class Cost static then it won't be collected by JUnit.

What should I do to resolve this issue?

Womanish answered 21/12, 2017 at 22:50 Comment(0)
K
53

@TestInstance(PER_CLASS)

You may select the "single test instance per class" mode annotating the nested class with @TestInstance(TestInstance.Lifecycle.PER_CLASS):

class ColorTest {

    @Nested
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    class Inner {

        @ParameterizedTest
        @MethodSource("colors")
        void blue(Color color, int blue) {
            Assertions.assertEquals(color.getBlue(), blue);
        }

        Stream<Arguments> colors() {
            return Stream.of(
                    Arguments.of(Color.BLACK, 0),
                    Arguments.of(Color.GRAY, 128),
                    Arguments.of(Color.BLUE, 255)
            );
        }
    }

}

When using this mode, a new test instance will be created once per test class.

ArgumentsProvider

Or you may switch from a MethodSource to an ArgumentsProvider.

I modified your example to see if it compiles and runs locally:

class ColorTest {

    static class Blues implements ArgumentsProvider {

        @Override
        public Stream<Arguments> provideArguments(ExtensionContext context) {
            return Stream.of(
                    Arguments.of(Color.BLACK, 0),
                    Arguments.of(Color.GRAY, 128),
                    Arguments.of(Color.BLUE, 255)
            );
        }
    }

    @Nested
    class Inner {

        @ParameterizedTest
        @ArgumentsSource(Blues.class)
        void blue(Color color, int blue) {
            Assertions.assertEquals(color.getBlue(), blue);
        }
    }

}

More details at http://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests

Kantos answered 21/12, 2017 at 23:12 Comment(5)
I think in this case test data and the test are logically spitted. I don't sure that is a good idea.Womanish
Added second solution that a) allows a non-static method source and b) ties the test data and logic in-place. Caveat: when using this mode, a new test instance will be created once per test class.Kantos
It annoys me that Java won't just let me have a static method in my inner non-static class. Seems like a bit of an arbitrary decision to me.Fanatic
@Fanatic as of Java 16 it is possible to have static methods in non-static inner classes.Reciprocate
@ValerijDobler nice, maybe I'll get to use that some day soon.Fanatic
J
24

Another variation based on JUnit 5.2.0 is this.

class ColorTest {

public static Stream<Arguments> provideColors() {
    return Stream.of(
            Arguments.of(Color.BLACK, 0),
            Arguments.of(Color.GRAY, 128),
            Arguments.of(Color.BLUE, 255)
    );
}

@Nested
class Inner {

    @ParameterizedTest
    @MethodSource("com.domain.ColorTest#provideColors")
    void blue(Color color, int blue) {
        Assertions.assertEquals(color.getBlue(), blue);
    }
}

}

Jeroboam answered 25/9, 2020 at 14:58 Comment(2)
It works well. To note that it will work even if the method provideColors will be private (and static).Zigzag
Note that IntelliJ IDEA does not support code completion or references of fully qualified class and methods yetLex
K
8

A bit late to this game, but...

You can implement the provider, as a static, in the outer class. Then, in @MethodSource, you simply need to provide the fully qualified name of the parameter (i.e., com.biz.pckg#colors).

This is documented in the JUnit user guide.

Kwang answered 15/11, 2018 at 23:0 Comment(1)
Yes, this answers the original question. A more complete example is here: #53976105 Basically, in addition to the fqcn, you need to know that you use # not .Manufacturer
S
1

Here is the solution for Kotlin:

class MyContainerTest {

    @Nested
    @TestInstance(Lifecycle.PER_CLASS)
    inner class MyNestedTest {
 
        @ParameterizedTest
        @MethodSource("generateArgs")
        fun `Test should work`(
            argument: String
        ) {
            // Use the argument
        }

        private fun generateArgs() = listOf(
            "abc",
            "xyz",
            "1234"
        )
    }
}
Sidonius answered 15/9, 2023 at 4:45 Comment(0)
E
0

If you have several nesting, you can provide the fully qualified path to the static method. E.g.

package com.foo.bar;

class MyRootTestClass {

  @Nested
  class Nested1 {
  
    @Nested
    class Nested2 {
      
      public static Stream<Arguments> getArgs() {
        return Stream.of(
                        Arguments.of("hello", 123),
                        Arguments.of("World", 456)
                );
      }       

      @ParameterizedTest
      @MethodSource("com.foo.bar.MyRootTestClass" +
                    "$Nested1" +
                    "$Nested2#getArgs")
      void myTest(String text, Integer int) {
      
      }

    } // Nested1

  } // Nested2

} // MyRootTestClass

Pay attention to the nested classes separator '$' in the path and the method separator '#'

Eau answered 11/1 at 16:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.