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

时间:2015-06-17 15:17:25

标签: java junit guice

我想使用Guice和GuiceBerry将非静态遗留服务注入工厂类。然后我想将该工厂注入我的Parameterized 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

    }
}

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

4 个答案:

答案 0 :(得分:2)

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

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

@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
  }
}

编辑:枚举代码:

public enum RatingType {
  CLASSIC(1002),
  COMPLEX(1020);

  private final int ratingId;

  private RatingType(int ratingId) {
    this.ratingId = ratingId;
  }

  // option 1: keep rating ID private by having a method like this
  public get(RatingService ratingService) {
    return ratingService.getRatingById(ratingId);
  }

  // option 2: have a package-scope accessor
  int getRatingId() {
    return ratingId;
  }
}

编辑:如果您使用选项2,那么您将添加一个新方法以从Rating获取RatingType,该ratingId将委托给通过{{1的服务}}:

@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()

的枚举中使用方法
public enum RatingType {
  CLASSIC {
    @Override public Rating getRating(Ratings ratings) {
      return ratings.getClassicRating();
    }
  },
  COMPLEX {
    @Override public Rating getRating(Ratings ratings) {
      return ratings.getComplexRating();
    }
  };

  public abstract Rating getRating(Ratings ratings);
}

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

这假设您可以编写应该​​传递给所有Rating个实例的测试。

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

答案 1 :(得分:1)

我的解决方案是添加一个包含整数的RatingId类并创建一个工厂RatingIds,然后我可以返回静态并用作参数。我在getRatingById界面中重载RatingService方法以接受新的RatingId类型,然后将评分服务注入我的测试并直接使用它。

添加工厂:

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

测试:

@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

    }
}

答案 2 :(得分:0)

在像您这样的情况下,已预先知道生成的参数集的总数,但是构建参数本身需要一定的上下文(例如,使用Spring自动装配服务实例),您可以采用功能性方法(使用junit5和参数化)< / p>

如果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.
    }
}

行家部:

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

答案 3 :(得分:-1)

我没有运行guiceberry(古代依赖项),但是使用JUnitParamters和简单的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服务很容易做到这一点。