Using non-static injected services in JUnit Parameterized tests
I want to use Guice and GuiceBerry to inject a non-static legacy service into a factory class. Then I want to add that factory to my JUnit test parameter.
However, the JUnit issue requires the method to @Parameters
be static.
Factory example:
@Singleton
public class Ratings {
@Inject
private RatingService ratingService;
public Rating classicRating() {
return ratingService.getRatingById(1002)
}
// More rating factory methods
}
An example of using the test:
@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
}
}
I've tried requesting static injection for the factory method, but the method Parameters
is called before GuiceBerry @Rule
. I've also considered using just the rating id as parameters, but I want to find a reusable solution. Maybe my approach is flawed?
source to share
My solution was to add a class RatingId
that wraps an integer and creates a factory RatingIds
, after which I can return static and use as parameters. I have overloaded the method getRatingById
in my interface RatingService
to accept the new type RatingId
and then inject the grading service into my test and use it directly.
Added factory:
public class RatingIds {
public static RatingId classic() {
return new RatingId(1002);
}
// Many more
}
Test:
@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
}
}
source to share
Unfortunately JUnit must be able to list all tests before running any tests, so the parameters method must be called before rules.
You can define an enum for the rating type:
@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
}
}
Edit: Code to enumerate:
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;
}
}
Edit: if you go with option 2, you add a new method to get Rating
from RatingType
that delegates the transfer of services ratingId
:
@Singleton
public class Ratings {
@Inject
private RatingService ratingService;
public Rating getRating(RatingType ratingType) {
return ratingService.getRatingById(
ratingType.getRatingId());
}
// More rating factory methods
}
If you don't want to RatingType
be in your public API, you can define it in your test and have a method in an enum namedgetRating()
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);
}
You can also create a value type instead of an enumeration.
It is assumed that you can write tests that must pass for all instances Rating
.
If you have generic tests, but some tests to score , I would make an abstract base class containing generic tests and an abstract createRating()
method and subclass for each rating type.
source to share
In cases like yours where the total number of generated parameter sets is known in advance, but some context is required to build the parameters themselves (for example, an autoconnect service instance with Spring), you can use a functional approach (with junit5 & parameterized)
Obviously this doesn't work if the createParameter
function createParameter
depends on a context like this: - /
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.
}
}
Maven Dep:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
source to share
I didn't manage to use guicberry (ancient dependencies), but using JUnitParamters and a simple example, it's pretty straightforward:
@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));
}
}
If you check the JUnitParams website, you will find many other ways to define a parameter list. It's really easy to do this with an injection service.
source to share