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?
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)
}
}
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.
Related
I am using Scala 2.10 with ScalaMock 3.6.
I have a quite simple test case with 4 test scenarios. I have created a mock object for those tests to use (imitating file system):
class ProcessingOperatorTest extends FlatSpec with Matchers with BeforeAndAfterEach with MockFactory {
...
val fakeFS = mock[FileIO]
(fakeFS.createFile _).expects(*).returns(true).anyNumberOfTimes()
(fakeFS.exist _).expects(where { (p: String) => p.contains(existing) }).returns(true).anyNumberOfTimes()
(fakeFS.exist _).expects(where { (p: String) => p.contains(notExisting) }).returns(false).anyNumberOfTimes()
behavior of "Something"
it should "test 1" in {
...
}
it should "test 2" in {
...
}
it should "test 3" in {
...
}
it should "test 4" in {
...
}
Now:
1st test does not use any of mocked methods (but needs the mock object)
2nd test uses only existing mock method
3rd test uses both existing and not existing mock methods
4th test uses all methods, (also createFile)
Now, for some reason, when running all those tests together, 4th test fails giving me the following error. If running separately, it will pass.
Unexpected call: <mock-1> FileIO.exist(notExisting)
Expected:
inAnyOrder {
}
Actual:
<mock-1> FileIO.exist(notExisting)
ScalaTestFailureLocation: scala.Option at (Option.scala:120)
org.scalatest.exceptions.TestFailedException: Unexpected call: <mock-1> FileIO.exist(notExisting)
...
The other walkaround is to copy-paste the mock declaration and its behaviour inside 4th it should { ... } test scenario. Tests work then (separately, and all together).
Why is global mock instance failing?
I can try to prepare a similar test scenario as separate sbt project if needed.
Mix in org.scalatest.OneInstancePerTest as described here:
class ProcessingOperatorTest extends FlatSpec
with Matchers
with BeforeAndAfterEach
with MockFactory
with OneInstancePerTest {
...
}
If I don't actually have an explicit assertions like count must_== 1 a in Specs2 test, I'd get an error indicating no implicit could be found.
// doesn't compile
class Example extends Specification {
"You need an assertion" >> {
// hello!
}
}
Fair enough.
But if I also use scalamock's MockContext, I can rely on just expectations rather than assertions; mock something and scalamock will verify methods are called etc;
class MockExample extends Specification {
"I can use 'expectations' here instead" in new MockContext {
val foo = mock[Foo]
(foo.bar _).expects(*).once
// no explicit assertions
}
}
However, if I try and share context setup by mixing in IsolatedMockFactory, I'm back to the compiler failure. Any ideas how to fix it?
// doesn't compile
class AnotherMockExample extends Specification with IsolatedMockFactory {
val foo = mock[Foo]
"I can't use 'expectations' here any more" >> {
(foo.bar _).expects(*).once
}
}
An example in specs2 accepts anything that has an org.specs2.execute.AsResult typeclass instance. Since (foo.bar _).expects.once is of type CallHandler you can create an AsResult instance for CallHandler that just evaluates the value and returns Success
implicit def CallHandlerAsResult[R : Defaultable]: AsResult[CallHandler[R]] = new AsResult {
def asResult(c: =>CallHandler[R]) = {
c
Success
}
}
Since failures are exception-based in ScalaMock this should result in an exception being thrown if some mock expectation is not satisfied.
In our project we use Scala Specs2 together with Selenium.
I'm trying to implement screenshot-on-failure mechanism "in a classic way (link)" for my tests, using JUnit annotations, but, the rule doesn't called on test failure at all.
The structure of the test is as follows:
class Tests extends SpecificationWithJUnit{
trait Context extends LotsOfStuff {
#Rule
val screenshotOnFailRule = new ScreenshotOnFailRule(driver)
}
"test to verify stuff that will fail" should {
"this test FAILS" in new Context {
...
}
}
The ScreenshotOnFailRule looks like this:
class ScreenshotOnFailRule (webDriver: WebDriver) extends TestWatcher {
override def failed(er:Throwable, des:Description) {
val scrFile = webDriver.asInstanceOf[TakesScreenshot].getScreenshotAs(OutputType.FILE)
FileUtils.copyFile(scrFile, new File(s"/tmp/automation_screenshot${Platform.currentTime}.png"))
}
}
I understand that probably it doesn't work now because the tests aren't annotated with #Test annotation.
Is it possible to annotate the Specs2 tests with JUnit #Rule annotation?
According to this question it seems as if JUnit Rules aren't supported. But you could try to make use of the AroundExample trait:
import org.specs2.execute.{AsResult, Result}
import org.specs2.mutable._
import org.specs2.specification.AroundExample
class ExampleSpec extends Specification with AroundExample {
// execute tests in sequential order
sequential
"The 'Hello world' string" should {
"contain 11 characters" in {
"Hello world" must have size (10)
}
// more tests..
}
override protected def around[T](t: => T)(implicit ev: AsResult[T]): Result = {
try {
AsResult.effectively(t)
} catch {
case e: Throwable => {
// take screenshot here
throw e
}
}
}
}
I am trying to test some db dependent stuff with specs2 in scala. The goal is to test for "db running" and then execute the test. I figured out that i can use orSkip from the Matcher class if the db is down.
The problem is, that i am getting output for the one matching condition (as PASSED) and the example is marked as SKIPPED. What i want instead: Only execute one test that is marked as "SKIPPED" in case the test db is offline. And here is the code for my "TestKit"
package net.mycode.testkit
import org.specs2.mutable._
import net.mycode.{DB}
trait MyTestKit {
this: SpecificationWithJUnit =>
def debug = false
// Before example
step {
// Do something before
}
// Skip the example if DB is offline
def checkDbIsRunning = DB.isRunning() must be_==(true).orSkip
// After example
step {
// Do something after spec
}
}
And here the code for my spec:
package net.mycode
import org.specs2.mutable._
import net.mycode.testkit.{TestKit}
import org.junit.runner.RunWith
import org.specs2.runner.JUnitRunner
#RunWith(classOf[JUnitRunner])
class MyClassSpec extends SpecificationWithJUnit with TestKit with Logging {
"MyClass" should {
"do something" in {
val sut = new MyClass()
sut.doIt must_== "OK"
}
"do something with db" in {
checkDbIsRunning
// Check only if db is running, SKIP id not
}
}
Out now:
Test MyClass should::do something(net.mycode.MyClassSpec) PASSED
Test MyClass should::do something with db(net.mycode.MyClassSpec) SKIPPED
Test MyClass should::do something with db(net.mycode.MyClassSpec) PASSED
And output i want it to be:
Test MyClass should::do something(net.mycode.MyClassSpec) PASSED
Test MyClass should::do something with db(net.mycode.MyClassSpec) SKIPPED
I think you can use a simple conditional to do what you want:
class MyClassSpec extends SpecificationWithJUnit with TestKit with Logging {
"MyClass" should {
"do something" in {
val sut = new MyClass()
sut.doIt must_== "OK"
}
if (DB.isRunning) {
// add examples here
"do something with db" in { ok }
} else skipped("db is not running")
}
}
Have you tried using the args(skipAll=true) argument? See a few examples here.
Unfortunately (as far as I know), you cannot skip a single example in a unit specification. You can, however, skip the specification structure with this argument like this, so you might have to create separate specifications:
class MyClassSpec extends SpecificationWithJUnit {
args(skipAll = false)
"MyClass" should {
"do something" in {
success
}
"do something with db" in {
success
}
}
}
A new feature addressing this has been added to specs 2.3.10.
I've inherited some JUnit tests written in scala that need to be fixed to use #BeforeClass semantics. I understand that the #BeforeClass annotation must be applied to static methods only. I understand that methods defined in "companion" objects (as opposed to scala classes) are static. How can I get a test method to be called once prior to the individual instance methods in a test class?
object TestClass {
#BeforeClass
def stuff() {
// beforeclass stuff
}
}
class TestClass {
#Test
...
}
seems to work...
I had to move to specs2 to implement this feature with Scala. Just adding an example to help people with the same problem as the orginal poster who don't yet know specs2.
The specs2 way uses the concept of a "step" to accomplish test suite set-up and tear-down. If you run with a JUnitRunner all your Ant scripts and IDEs that use JUnit will still know how to run it. Here's an example using a mutable specification from specs2:
import org.specs2.mutable.Specification
import org.junit.runner.RunWith
import org.specs2.runner.JUnitRunner
#RunWith(classOf[JUnitRunner])
class MutableSpecs2ExampleTest extends Specification {
var firstStep: String = null
var secondStep: String = null
var thirdStep: String = null
//Steps are guaranteed to run serially
step{println("Loading Spring application context...");firstStep="Hello World"}
step{println("Setting up mocks...");secondStep = "Hello Scala"}
//The fragments should be run in parallel by specs2
"Some component Foo in my project" should{
" pass these tests" in {
println("Excersizing some code in Foo")
firstStep must startWith("Hello") and endWith("World")
}
" pass theses other tests" in {
println("Excersizing some other code in Foo")
firstStep must have size(11)
}
}
"Some component Bar in my project" should{
" give the correct answer" in {
println("Bar is thinking...")
secondStep must startWith("Hello") and endWith("Scala")
thirdStep must be equalTo null
}
}
step{println("Tearing down resources after tests...");thirdStep = "Hello Specs2"}
}
And here's an example with a non-mutable specification:
import org.specs2.Specification
import org.specs2.specification.Step
import org.junit.runner.RunWith
import org.specs2.runner.JUnitRunner
#RunWith(classOf[JUnitRunner])
class Specs2ExampleTest extends Specification{
var firstStep: String = null
var secondStep: String = null
var thirdStep: String = null
def is =
"This is a test with some set-up and tear-down examples" ^
p^
"Initialize" ^
Step(initializeDependencies())^
Step(createTestData())^
"Component Foo should" ^
"perform some calculation X " !testX^
"perform some calculation Y" !testY^
p^
"Tidy up" ^
Step(removeTestData())^
end
def testX = {
println("testing Foo.X")
firstStep must be equalTo("Hello World")
}
def testY = {
println("testing Foo.Y")
secondStep must be equalTo("Hello Scala")
thirdStep must be equalTo null
}
def initializeDependencies(){
println("Initializing Spring applicaiton context...")
firstStep = "Hello World"
}
def createTestData(){
println("Inserting test data into the db...")
secondStep = "Hello Scala"
}
def removeTestData(){
println("Removing test data from the db...")
println("Tearing down resources...")
thirdStep = "Hello Specs2"
}
}
You don't specify if you mean inherited in the OO-programming sense or the "taken over from someone else" sense of the word.
In the latter case, I'd advise you to re-write the thing using either ScalaTest or Specs2 and expose it as a JUnit test (both frameworks support this) so that it'll integrate with whatever other tools and processes you already have in place.