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

问题描述 投票:3回答:4

我想使用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作为参数,但我想找到一个可重用的解决方案。也许我的方法有缺陷?

java junit guice
4个回答
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的服务:

@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
投票

我的解决方案是添加一个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

    }
}

0
投票

在你的情况下,预先知道生成的参数集的总数,但是构建参数本身需要一些上下文(例如,使用Spring的自动服务实例),你可以使用函数方法(使用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.
    }
}

胃潴留:

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

-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服务很容易做到这一点。

© www.soinside.com 2019 - 2024. All rights reserved.