I'm using Spring to inject the path to a directory into my unit tests. Inside this directory are a number of files that should be used to generate test data for parameterized test cases using the Parameterized test runner. Unfortunately, the test runner requires that the method that provides the parameters be static. This doesn't work for my situation because the directory can only be injected into a non-static field. Any ideas how I can get around this?
问题:
回答1:
I assume you are using JUnit 4.X since you mentioned the Parameterized test runner. This implies you aren't using @RunWith(SpringJUnit4ClassRunner). Not a problem, just listing my assumptions.
The following uses Spring to get the test files directory from the XML file. It doesn't inject it, but the data is still available to your test. And in a static method no less.
The only disadvantage I see is that it may mean your Spring config is getting parsed/configured multiple times. You could load just a smaller file with test specific info if need be.
@RunWith(Parameterized.class)
public class MyTest {
@Parameters
public static Collection<Object[]> data() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/jeanne/jeanne.xml");
String dir = ctx.getBean("testFilesDirectory", String.class);
// write java code to link files in test directory to the array
return Arrays.asList(new Object[][] { { 1 } });
}
// rest of test class
}
回答2:
You can use a TestContextManager from Spring. In this example, I'm using Theories instead of Parameterized.
@RunWith(Theories.class)
@ContextConfiguration(locations = "classpath:/spring-context.xml")
public class SeleniumCase {
@DataPoints
public static WebDriver[] drivers() {
return new WebDriver[] { firefoxDriver, internetExplorerDriver };
}
private TestContextManager testContextManager;
@Autowired
SomethingDao dao;
private static FirefoxDriver firefoxDriver = new FirefoxDriver();
private static InternetExplorerDriver internetExplorerDriver = new InternetExplorerDriver();
@AfterClass
public static void tearDown() {
firefoxDriver.close();
internetExplorerDriver.close();
}
@Before
public void setUpStringContext() throws Exception {
testContextManager = new TestContextManager(getClass());
testContextManager.prepareTestInstance(this);
}
@Theory
public void testWork(WebDriver driver) {
assertNotNull(driver);
assertNotNull(dao);
}
}
I found this solution here : How to do Parameterized/Theories tests with Spring
回答3:
For someone reading this late 2015 or later, Spring 4.2 has, in addition to SpringJUnit4ClassRunner added SpringClassRule and SpringMethodRule which leverage the support for Spring TestContext Framework.
This means first class support for any Runner like MockitoJUnitRunner
or Parameterized
:
@RunWith(Parameterized.class)
public class FibonacciTest {
@ClassRule public static final SpringClassRule SCR = new SpringClassRule();
@Rule public final SpringMethodRule springMethodRule = new SpringMethodRule();
long input;
long output;
public FibonacciTest(long input, long output) { this.input = input; ...}
@Test
public void testFibonacci() {
Assert.assertEquals(output, fibonacci(input));
}
public List<Long[]> params() {
return Arrays.asList(new Long[][] { {0, 0}, {1, 1} });
}
}
回答4:
It's enough to annotate test class with @RunWith(Parameterized.class)
and @ContextConfiguration
, use @Autowired
for dependency injection and use TestContextManager
in constructor for initialization, e.g.:
@RunWith(Parameterized.class)
@ContextConfiguration(classes = TestConfig.class)
public class MyTest {
@Autowired
private DataSource dataSource;
private final int param;
@Parameterized.Parameters
public static List<Object[]> params() {
return Arrays.asList(new Object[][]{
{1},
{2},
});
}
public MyTest(int p) {
this.param = p;
new TestContextManager(getClass()).prepareTestInstance(this);
}
@Test
public void testSomething() {
…
}
}
回答5:
I use the following solution with the Parameterized.class without any problem: http://bmocanu.ro/coding/320/combining-junit-theoriesparameterized-tests-with-spring/
@ContextConfiguration(value = "classpath:test-context.xml")
public abstract class AbstractJunitTest extends AbstractJUnit4SpringContextTests {
private static TestContextManager testContextManager = null;
private static DAOFactory daoFactory = null;
@Before
public void initApplicationContext() throws Exception {
if (testContextManager == null) {
testContextManager = new TestContextManager(getClass());
testContextManager.prepareTestInstance(this);
daoFactory = (DAOFactory)applicationContext.getBean("daoFactory");
}
}
protected DAOFactory getDaoFactory() throws Exception {
return daoFactory;
}
}
@RunWith(Parameterized.class)
public class SomeTestClass extends AbstractJunitTest {
...
}
回答6:
Here is a first solution without JUnit 4.12 parameterized factory, below an improved solution with it.
Static context without transactional support
Let Spring do all configuration parsing and autowiring with TestContextManager class.
The trick is to use a fake test instance to get autowired fields and pass them to the parameterized test which will effectively run.
But keep in mind prepareTestInstance() do the autowiring but doesn't manage test transaction and other nice stuffs handled by beforeTestMethod()
and afterTestMethod()
.
@RunWith(Parameterized.class)
@ContextConfiguration(locations = {"/test-context.xml", "/mvc-context.xml"})
@WebAppConfiguration
@ActiveProfiles("test-profile")
public class MyTest {
@Parameters
public static Collection<Object[]> params() throws Exception {
final MyTest fakeInstance = new MyTest();
final TestContextManager contextManager = new TestContextManager(MyTest.class);
contextManager.prepareTestInstance(fakeInstance);
final WebApplicationContext context = fakeInstance.context;
// Do what you need with Spring context, you can even access web resources
final Resource[] files = context.getResources("path/files");
final List<Object[]> params = new ArrayList<>();
for (Resource file : files) {
params.add(new Object[] {file, context});
}
return params;
}
@Parameter
public Resource file;
@Autowired
@Parameter(1)
public WebApplicationContext context;
}
However a drawback appear if you have a lot of autowired fields because you have to manually pass them to the array parameters.
Parameterized factory with full Spring support
JUnit 4.12 introduce ParametersRunnerFactory which allow to combine parameterized test and Spring injection.
public class SpringParametersRunnerFactory implements ParametersRunnerFactory {
@Override
public Runner createRunnerForTestWithParameters(TestWithParameters test) throws InitializationError {
final BlockJUnit4ClassRunnerWithParameters runnerWithParameters = new BlockJUnit4ClassRunnerWithParameters(test);
return new SpringJUnit4ClassRunner(test.getTestClass().getJavaClass()) {
@Override
protected Object createTest() throws Exception {
final Object testInstance = runnerWithParameters.createTest();
getTestContextManager().prepareTestInstance(testInstance);
return testInstance;
}
};
}
}
The factory can be added to previous test class to give full Spring support like test transaction, reinit dirty context and servlet test. And of course there no more need to pass autowired fields from fake test instance to parameterized test.
@UseParametersRunnerFactory(SpringParametersRunnerFactory.class)
@RunWith(Parameterized.class)
@ContextConfiguration(locations = {"/test-context.xml", "/mvc-context.xml"})
@WebAppConfiguration
@Transactional
@TransactionConfiguration
public class MyTransactionalTest {
@Parameters
public static Collection<Object[]> params() throws Exception {
final MyTransactionalTest fakeInstance = new MyTransactionalTest();
final TestContextManager contextManager = new TestContextManager(MyTransactionalTest.class);
contextManager.prepareTestInstance(fakeInstance);
final WebApplicationContext context = fakeInstance.context;
// Do what you need with Spring context, you can even access web resources
final Resource[] files = context.getResources("path/files");
final List<Object[]> params = new ArrayList<>();
for (Resource file : files) {
params.add(new Object[] {file});
}
return params;
}
@Parameter
public Resource file;
@Autowired
private WebApplicationContext context;
}
回答7:
Remember that Spring inject using @Autowired, but also with setter. So instead of using @Autowired, use the setter:
private static String directory;
public void setDirectory(String directory) {
this.directory = directory;
}
public static String getDirectory() {
return directory;
}