JUnit4, Selenium and Spring test context
In a previous post I wrote about using JUnit4 with Selenium.
So how about injecting your JUnit run listener via Spring?
My first attempt modified my runner to be a subclass of SpringJUnit4ClassRunner
Then I created the usual XML spring config file but named it after my test class name. So for
FooTests I created FooTests-context.xml. If you want another name you can pass that into
@ContextConfiguration.
Here is a bit of it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<bean id="selenium" class="com.thoughtworks.selenium.DefaultSelenium"> <constructor-arg> <value>localhost</value> </constructor-arg> <constructor-arg> <value>4444</value> </constructor-arg> <constructor-arg> <value>*firefox</value> </constructor-arg> <constructor-arg> <value><a href="http://localhost:9082/ws/" style="color: #006620; background-color: #fff9ab" class="linkification-ext" title="Linkification: http://localhost:9082/ws/">http://localhost:9080/myapp/</a></value> </constructor-arg> </bean> |
Here is is what I have for the test cases
1 2 3 4 5 6 7 8 9 10 |
@RunWith(SpringRunner.class) @ContextConfiguration public class FooTests() { @Autowired private Selenium selenium; @Test public void whatever() {} } |
and the JUnit 4 listener:
1 2 3 4 5 |
public class SpringRunListener extends RunListener { @Autowired // wishful thinking private Selenium selenium; etc. |
So there is the problem. Spring will autowire testcases but not the runners nor the listeners.
What is the Spring way? Forget the JUnit RunListener. You write a Spring TestExecutionListener
.
But that’s not autowired either so you have to grab your beans yourself. Luckily you have access to
the Spring context.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class MyTestExecutionListener extends AbstractTestExecutionListener { private static final Log logger = LogFactory.getLog(MyTestExecutionListener.class); Selenium selenium; @Override public void beforeTestMethod(TestContext testContext) throws Exception { // not autowired so go grab it selenium = (Selenium) testContext.getApplicationContext().getBean("Selenium"); } @Override public void afterTestMethod(TestContext testContext) throws Exception { Throwable t = testContext.getTestException(); if (t != null) { report(t); // go ahead and do the screendump using selenium } } |
and to make your listener get used you add it to the list of test execution listeners in your test classes.
I put it in the superclass of my test classes.
1 2 3 4 5 6 7 8 9 10 |
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @TestExecutionListeners( { MyTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class }) public class FooTests ... |
But you say, what about that new @Configurable annotation?
Can’t you use DI on an object that Spring does not instantiate?
Stay tuned.
I’m interested in hearing more about your Spring/Selenium RC adventures. I’m getting ready to rewrite quite a few Selenium tests into using RC and taking the opportunity to create more manageable and reusable code. I’m a QA person by trade so I’m fuzzy on design pattern best practices, but I know from experience that good code makes life easier for everyone.
Inspired by this post I made a class FooTests containing some dummy tests and a DummyTestExecutionListener that printed some stuff in the methods of the TestExecutionListener.
Unfortunately I have to conclude that your TestExecutionListener alone will not cover exceptions in the constructor of the test class, neither in the @junit.org.Before (and I presume, neither in the @After)…
Just as a comment,
By convention, a Spring bean name should start lowercase: “selenium” instead of “Selenium”
Cheers,
Fran