Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I integrate ScalaTest with Spring

I need to populate my ScalaTest tests with @Autowired fields from a Spring context, but most Scalatest tests (eg FeatureSpecs 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") {...}
like image 998
Duncan McGregor Avatar asked Jun 08 '11 17:06

Duncan McGregor


3 Answers

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") {...}
  }
}
like image 97
Duncan McGregor Avatar answered Oct 30 '22 13:10

Duncan McGregor


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)
  }
}
like image 20
Lance Arlaus Avatar answered Oct 30 '22 14:10

Lance Arlaus


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" )
  }
}
like image 37
DeegC Avatar answered Oct 30 '22 12:10

DeegC