3

I want to set up a test in Scala that creates a mock config to provide certain values. I'm using ScalaTest 3.0.1, ScalaMock 3.4.2, and typesafe 1.3.1. The goal is to mock values for the config before running tests. The docs at http://www.scalatest.org/user_guide/testing_with_mock_objects and http://scalamock.org/user-guide/features/ seem to offer a couple options. First, here's some target code:

import com.typesafe.config.Config
class Sample(config: Config) {
    private val aValue = config.getDouble("aValue")
}

It seems like it should be possible to set everything up one time, or to set up everything before each test. This attempt fails:

class SampleSpec extends FlatSpec with MockFactory with BeforeAndAfterAll {

  private val mockConfig = mock[Config]

  override def beforeAll {
    (mockConfig.getDouble _).expects("aValue").returning(1.0).anyNumberOfTimes()
  }

  "testOne" should "return 1" in {
    new Sample(mockConfig)
  }

  "testTwo" should "return 1" in {
    new Sample(mockConfig)
  }

}

The first test succeeds but second test in the fixture fails, and this error is produced:

Unexpected call: <mock-1> Config.getDouble(aValue)

Expected:
inAnyOrder {

}

Actual:
  <mock-1> Config.getDouble(aValue)
ScalaTestFailureLocation: scala.Option at (Option.scala:120)
org.scalatest.exceptions.TestFailedException: Unexpected call: <mock-1> Config.getDouble(aValue)

Expected:
inAnyOrder {

}

Here's an alternative approach:

class SampleSpec extends FlatSpec with MockFactory with BeforeAndAfter {

  private val mockConfig = mock[Config]

  before {
    (mockConfig.getDouble _).expects("aValue").returning(1.0)
  }

  "testOne" should "return 1" in {
    new Sample(mockConfig)
  }

  "testTwo" should "return 1" in {
    new Sample(mockConfig)
  }

}

It produces this exception:

An exception or error caused a run to abort: assertion failed: Null expectation context - missing withExpectations?
java.lang.AssertionError: assertion failed: Null expectation context - missing withExpectations?

Why does the first attempt fail? The test specifies that getDouble can be invoked any number of times, yet the second test fails as if anyNumberOfTimes() wasn't used. How should this be coded so that the method can be mocked once and invoked repeatedly? Why does the second attempt fail? Is there a method to reset the mock so it can be reused?

Don Branson
  • 13,631
  • 10
  • 59
  • 101

2 Answers2

3

I'd also like to point out the documentation page for this, with a slightly different style (using a trait):

http://scalamock.org/user-guide/sharing-scalatest/#fixture-contexts

For example:

class SampleSpec extends FlatSpec with OneInstancePerTest with MockFactory {

  private val mockConfig = mock[Config]

  (mockConfig.getDouble _).expects("aValue").returning(1.0).anyNumberOfTimes

  "testOne" should "return 1" in {
    new Sample(mockConfig)
  }

  "testTwo" should "return 1" in {
    new Sample(mockConfig)
  }

}
Don Branson
  • 13,631
  • 10
  • 59
  • 101
Philipp
  • 967
  • 6
  • 16
  • I applied the first technique (OneInstancePerTest), and like this better. I've seen docs for this elsewhere, but this explanation is clearer. Editing to provide an example... – Don Branson Jan 27 '17 at 14:58
1

Recreate the mock every single time, manually is the only way I can get it to work:

class SampleSpec extends FlatSpec with MockFactory {

  private def mockConfig = {
    val mocked = mock[Config]
    (mocked.getDouble _).expects("aValue").returning(1.0).anyNumberOfTimes()
    mocked
  }

  "testOne" should "return 1" in {
    new Sample(mockConfig)
  }

  "testTwo" should "return 1" in {
    new Sample(mockConfig)
  }

}

which is easy because your tests don't change at all. You're simply moving the logic from the "globally local" variable and into the local scope of an individual test.

Don Branson
  • 13,631
  • 10
  • 59
  • 101
wheaties
  • 35,646
  • 15
  • 94
  • 131