Java 在JUnit参数化测试中使用非静态注入服务

Java 在JUnit参数化测试中使用非静态注入服务,java,junit,guice,Java,Junit,Guice,我想使用Guice和GuiceBerry将非静态遗留服务注入工厂类。然后,我想将该工厂注入到我的参数化JUnit测试中 但是,问题是JUnit要求@Parameters方法是静态的 示例工厂: @Singleton public class Ratings { @Inject private RatingService ratingService; public Rating classicRating() { return ratingService

我想使用Guice和GuiceBerry将非静态遗留服务注入工厂类。然后,我想将该工厂注入到我的参数化JUnit测试中

但是,问题是JUnit要求
@Parameters
方法是静态的

示例工厂:

@Singleton
public class Ratings {
    @Inject
    private RatingService ratingService;

    public Rating classicRating() {
         return ratingService.getRatingById(1002)
    }

    // More rating factory methods
}
测试使用示例:

@RunWith(Parameterized.class)
public class StaticInjectParamsTest {
    @Rule
    public GuiceBerryRule guiceBerryRule = new GuiceBerryRule(ExtendedTestMod.class)

    @Inject
    private static Ratings ratings;

    @Parameter
    public Rating rating;

    @Parameters
    public static Collection<Rating[]> ratingsParameters() {
    return Arrays.asList(new Rating[][]{
            {ratings.classicRating()}
            // All the other ratings
        });
    }

    @Test
    public void shouldWork() {
        //Use the rating in a test

    }
}
@RunWith(参数化的.class)
公共类staticParamsTest{
@统治
public GuiceBerryRule GuiceBerryRule=new GuiceBerryRule(ExtendedTestMod.class)
@注入
私人静态评级;
@参数
公众评级;
@参数
公共静态收集比率参数(){
返回数组.asList(新评级[][]{
{ratings.classicRating()}
//所有其他评级
});
}
@试验
公共空间应该工作{
//在测试中使用额定值
}
}

我已经尝试请求工厂方法的静态注入,但是在GuiceBerry
@Rule
之前调用了
参数
方法。我也考虑过只使用评级的Id作为参数,但我想找到一个可重用的解决方案。也许我的方法有缺陷

不幸的是,JUnit需要能够在运行任何测试之前枚举所有测试,因此必须在调用规则之前调用parameters方法

您可以为评级类型定义枚举:

@RunWith(Parameterized.class)
public class StaticInjectParamsTest {
  @Rule
  public GuiceBerryRule guiceBerryRule
      = new GuiceBerryRule(ExtendedTestMod.class);

  @Inject
  private Ratings ratings;

  @Parameter
  public RatingType ratingType;

  @Parameters
  public static Collection<RatingType> types() {
    return Arrays.asList(RatingType.values());
  }

  @Test
  public void shouldWork() {
    Rating rating = ratings.get(ratingType);
    // Use the rating in a test
  }
}
编辑:如果您选择选项2,您将添加一个新方法,从
RatingType
获得
评级,该评级将委托给通过
ratingId
的服务:

@Singleton
public class Ratings {
    @Inject
    private RatingService ratingService;

    public Rating getRating(RatingType ratingType) {
      return ratingService.getRatingById(
          ratingType.getRatingId());
    }

    // More rating factory methods
}
如果不希望
RatingType
出现在公共API中,可以在测试中定义它,并在枚举中使用名为
getRating()

还可以创建值类型而不是枚举

这假设您可以为所有
评级
实例编写应通过的测试


如果您有一些常见测试,但有一些特定于评级的测试,我将创建一个包含常见测试的抽象基类,以及一个抽象的createRating()
方法,并为每个评级类型对其进行子类化。

我没有让guiceberry运行(古老的依赖项),而是使用简单的guice,这相当简单:

@RunWith(JUnitParamsRunner.class)
public class GuiceJunitParamsTest {

    public static class SquareService {
        public int calculate(int num) {
            return num * num;
        }
    }

    @Inject
    private SquareService squareService;

    @Before
    public void setUp() {
        Guice.createInjector().injectMembers(this);
    }

    @Test
    @Parameters({ "1,1", "2,4", "5,25" })
    public void calculateSquares(int num, int result) throws Exception {
        assertThat(squareService.calculate(num), is(result));
    }
}

如果您查看JUnitParams网站,您将发现许多其他方法来定义参数列表。使用injecte服务很容易做到这一点。

我的解决方案是添加一个包装整数的
RatingId
类,并创建一个工厂
RatingId
,然后返回static并用作参数。我在
RatingService
接口中重载了
getRatingById
方法,以接受新的
RatingId
类型,然后将评级服务注入到我的测试中并直接使用它

新增工厂:

public class RatingIds {
    public static RatingId classic() {
        return new RatingId(1002);
    }
    // Many more
}
测试:

@RunWith(参数化的.class)
公共类staticParamsTest{
@统治
public GuiceBerryRule GuiceBerryRule=new GuiceBerryRule(ExtendedTestMod.class)
@注入
私人评级服务评级服务
@参数
公共评级ID评级ID;
@参数
公共静态收集比率参数(){
返回数组.asList(新RatingId[][]{
{RatingIds.classic()}
//所有其他评级
});
}
@试验
公共空间应该工作{
Rating Rating=ratingService.getRatingById(ratingId.getValue())
//在测试中使用额定值
}
}

在您这样的情况下,生成的参数集的总数是预先知道的,但是构建参数本身需要一些上下文(例如,带有Spring的autowired服务实例),您可以使用函数方法(使用junit5和参数化)

显然,如果
createParameter
函数本身依赖于这样的上下文,则这不起作用:-/

class MyTestClass {

    // may be autowired, cannot be static but is required in parameter generation
    SomeInstance instance;

    private interface SomeParamBuilder { SomeParam build(SomeInstance i);}

    private static Stream<Arguments> createParamterFactories() {
         return Stream.of(
            Arguments.of((SomeParamBuilder)(i)->     
                            {
                                return new SomeParam(i);
                            })
                         );
    }

    // does not work, because SomeParam needs SomeInstance for construction
    // which is not available in static context of createParameters.
    //@ParameterizedTest(name = "[{index}] {0}")
    //@MethodSource("createParameters")
    //void myTest(SomeParam param) {
    //}


    @ParameterizedTest(name = "[{index}] {0}")
    @MethodSource("createParamterFactories")
    void myTest(SomeParamBuilder builder) {
        SomeParam param = builder.build(instance);
        // rest of your test code can use param.
    }
}
类MyTestClass{
//可以是自动连线的,不能是静态的,但在参数生成中是必需的
某个实例;
私有接口SomeParamBuilder{SomeParam构建(SomeInstance i);}
私有静态流CreateParameterFactorys(){
回流(
(i)->
{
返回新的SomeParam(i);
})
);
}
//不起作用,因为SomeParam需要SomeInstance进行构造
//在createParameters的静态上下文中不可用。
//@ParameterizeTest(name=“[{index}]{0}”)
//@MethodSource(“createParameters”)
//void myTest(SomeParam param){
//}
@ParameterizeTest(name=“[{index}]{0}”)
@MethodSource(“CreateParameterFactorys”)
void myTest(SomeParamBuilder生成器){
SomeParam param=builder.build(实例);
//测试代码的其余部分可以使用param。
}
}
马文副总裁:

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>5.2.0</version>
            <scope>test</scope>
        </dependency>

org.junit.jupiter
朱尼特朱庇特酒店
5.2.0
测试

它需要一个静态的“provider”方法…这和我这里遇到的问题是一样的。。。junitparams存在生命周期问题,无法正常工作。我将把这个答案留在这里,也许一旦问题得到解决,这将成为一个有效的选择。到目前为止,我将接受下一票。如果你想走这条路,为什么不让parameters方法返回整数数组的集合,而不修改
RatingService
?我没有在这里使用ints的原因是我现在有了一个可重用的工厂类,它以更人性化的格式跟踪ID。我不需要修改我的服务接口,但这是一个快速的默认方法。我仍然建议将RatingID设置为枚举。整数分级ID可以是枚举的一个字段。我更新了我的答案以显示这一点
@RunWith(Parameterized.class)
public class StaticInjectParamsTest {
    @Rule
    public GuiceBerryRule guiceBerryRule = new GuiceBerryRule(ExtendedTestMod.class)

    @Inject
    private RatingService ratingService

    @Parameter
    public RatingId ratingId;

    @Parameters
    public static Collection<RatingId[]> ratingsParameters() {
    return Arrays.asList(new RatingId[][]{
        {RatingIds.classic()}
        // All the other ratings
        });
    }

    @Test
    public void shouldWork() {
        Rating rating = ratingService.getRatingById(ratingId.getValue())
        //Use the rating in a test

    }
}
class MyTestClass {

    // may be autowired, cannot be static but is required in parameter generation
    SomeInstance instance;

    private interface SomeParamBuilder { SomeParam build(SomeInstance i);}

    private static Stream<Arguments> createParamterFactories() {
         return Stream.of(
            Arguments.of((SomeParamBuilder)(i)->     
                            {
                                return new SomeParam(i);
                            })
                         );
    }

    // does not work, because SomeParam needs SomeInstance for construction
    // which is not available in static context of createParameters.
    //@ParameterizedTest(name = "[{index}] {0}")
    //@MethodSource("createParameters")
    //void myTest(SomeParam param) {
    //}


    @ParameterizedTest(name = "[{index}] {0}")
    @MethodSource("createParamterFactories")
    void myTest(SomeParamBuilder builder) {
        SomeParam param = builder.build(instance);
        // rest of your test code can use param.
    }
}
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>5.2.0</version>
            <scope>test</scope>
        </dependency>