I need to populate my ScalaTest tests with @Autowired
fields from a Spring context, but most Scalatest tests (eg FeatureSpec
s can't be run by the SpringJUnit4ClassRunner.class
-
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="myPackage.UnitTestSpringConfiguration", loader=AnnotationConfigContextLoader.class)
public class AdminLoginTest {
@Autowired private WebApplication app;
@Autowired private SiteDAO siteDAO;
(Java, but you get the gist).
How do I populate @Autowired
fields from an ApplicationContext
for ScalaTest?
class AdminLoginFeatureTest extends FeatureSpec with GivenWhenThen with ShouldMatchersForJUnit {
@Autowired val app: WebApplication = null
@Autowired val siteDAO: SiteDAO = null
feature("Admin Login") {
scenario("Correct username and password") {...}
Use the TestContextManager
, as this caches the contexts so that they aren't rebuilt every test. It is configured from the class annotations.
@ContextConfiguration(
locations = Array("myPackage.UnitTestSpringConfiguration"),
loader = classOf[AnnotationConfigContextLoader])
class AdminLoginFeatureTest extends FeatureSpec with GivenWhenThen with ShouldMatchers {
@Autowired val app: WebApplication = null
@Autowired val siteDAO: SiteDAO = null
new TestContextManager(this.getClass()).prepareTestInstance(this)
feature("Admin Login") {
scenario("Correct username and password") {...}
}
}
I tried using Duncan's answer with Spring 4 + Scala 2.11 and I got the following error:
java.lang.IllegalStateException: Test class [TestGateway] has been configured with @ContextConfiguration's 'locations' (or 'value') attribute {GatewayContextConfiguration}, but AnnotationConfigContextLoader does not support resource locations.
I had to tweak his code to use a class when configuring the ContextConfiguration instead of a string:
@ContextConfiguration( classes = Array(classOf[GatewayContextConfiguration]),
loader = classOf[AnnotationConfigContextLoader])
class TestGateway extends FlatSpec with Matchers {
@Autowired val gpClient: IGlobalPropsWSClient = null
new TestContextManager(this.getClass()).prepareTestInstance(this)
"Echo" should "return what it was sent." in {
val gateway = new CasaWsGateway
gateway.echo("This is a test") should be ( "This is a test" )
}
}
Here's a version implemented as a stackable trait (so that you can have your own beforeAll()
and afterAll()
methods, if necessary) that uses TestContextManager
to complete the context lifecycle.
I tried the raw TestContextManager.prepareTestInstance()
solution suggested in other posts, but noticed that my contexts weren't getting closed which results in side effects and accumulating garbage after each successive test run when using the sbt console.
@ContextConfiguration(classes = Array(classOf[SomeConfiguration]))
class SomeTestSpec extends FlatSpec with TestContextManagement {
// Use standard Autowired Spring annotation to inject necessary dependencies
// Note that Spring will inject val (read-only) fields
@Autowired
val someDependency: SomeClass = null
"Some test" should "verify something" in {
// Test implementation that uses injected dependency
}
}
TestContextManagement Gist
import org.scalatest.{BeforeAndAfterAll, Suite}
import org.springframework.core.annotation.{AnnotatedElementUtils, AnnotationAttributes}
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.{TestContext, TestContextManager}
import org.springframework.test.context.support.DirtiesContextTestExecutionListener
import org.springframework.util.Assert
/**
* Manages Spring test contexts via a TestContextManager.
*
* Implemented as a stackable trait that uses beforeAll() and afterAll() hooks to invoke initialization
* and destruction logic, respectively.
* Test contexts are marked dirty, and hence cleaned up, after all test methods have executed.
* There is currently no support for indicating that a test method dirties a context.
*
* @see org.springframework.test.context.TestContextManager
*/
trait TestContextManagement extends BeforeAndAfterAll { this: Suite =>
private val testContextManager: TestContextManager = new TestContextManager(this.getClass)
abstract override def beforeAll(): Unit = {
super.beforeAll
testContextManager.registerTestExecutionListeners(AlwaysDirtiesContextTestExecutionListener)
testContextManager.beforeTestClass
testContextManager.prepareTestInstance(this)
}
abstract override def afterAll(): Unit = {
testContextManager.afterTestClass
super.afterAll
}
}
/**
* Test execution listener that always dirties the context to ensure that contexts get cleaned after test execution.
*
* Note that this class dirties the context after all test methods have run.
*/
protected object AlwaysDirtiesContextTestExecutionListener extends DirtiesContextTestExecutionListener {
@throws(classOf[Exception])
override def afterTestClass(testContext: TestContext) {
val testClass: Class[_] = testContext.getTestClass
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null")
val annotationType: String = classOf[DirtiesContext].getName
val annAttrs: AnnotationAttributes = AnnotatedElementUtils.getAnnotationAttributes(testClass, annotationType)
val hierarchyMode: DirtiesContext.HierarchyMode = if ((annAttrs == null)) null else annAttrs.getEnum[DirtiesContext.HierarchyMode]("hierarchyMode")
dirtyContext(testContext, hierarchyMode)
}
}
If you are using Spring boot you could use the TestContextManager (as other comments suggested) and the @SpringBootTest annotation.
This is how I test a controller with scalaTest and spring boot:
@RunWith(classOf[SpringRunner])
@SpringBootTest(webEnvironment = RANDOM_PORT)
class CustomerControllerIT extends FeatureSpec with GivenWhenThen with Matchers {
@Autowired
var testRestTemplate: TestRestTemplate = _
new TestContextManager(this.getClass).prepareTestInstance(this)
@LocalServerPort
val randomServerPort: Integer = null
val baseUrl = s"http://localhost:$randomServerPort"
feature("Customer controller") {
scenario("Find customer by id") {
Given("a customer id")
val id = 1
When("a request to /customers/{id} is sent")
val url = s"$baseUrl/customers/$id"
val response = testRestTemplate.getForEntity(url, classOf[Customer])
Then("we get a response with the customer in the body")
response.getBody.getId shouldBe 1
response.getBody.getName shouldBe "Bob"
}
}
}
Here there is a post about how to do integration tests and unit tests with spring boot and ScalaTest:
ignaciosuay.com/testing-spring-boot-with-scalatest/