User Guide - Sharing mocks and expectations in Specs2

Sharing mocks and expectations in Specs2

To use ScalaMock in Specs2 tests you can either:

Fixture contexts

Fixture context instances should be created on a per test-case basis and implement the MockContext trait.

Fixture contexts are more flexible and are recommended for complex test suites where a single set of fixtures does not fit all test cases.

Basic usage

For simple test cases it’s enough to run each test case in a new MockContext scope:

class BasicCoffeeMachineTest extends Specification {

   "CoffeeMachine" should {
        "not turn on the heater when the water container is empty" in new MockContext {
            val waterContainerMock = mock[WaterContainer]
            (waterContainerMock.isEmpty _).expects().returning(true)
            // ...
        }

        "not turn on the heater when the water container is overfull" in new MockContext {
            val waterContainerMock = mock[WaterContainer]
            // ...
        }
   }
}

Complex fixture contexts

When multiple test cases need to work with the same mocks (and more generally, the same fixtures: files, sockets, database connections, etc.) you can use fixture contexts that extends MockContext trait:

class CoffeeMachineTest extends Specification {

  trait Test extends MockContext { // fixture context
    // shared objects
    val waterContainerMock = mock[WaterContainer]
    val heaterMock = mock[Heater]
    val coffeeMachine = new CoffeeMachine(waterContainerMock, heaterMock)

    // test setup
    coffeeMachine.powerOn()
  }

  // you can extend and combine fixture-contexts
  trait OverfullWaterContainerTest extends Test {
    // you can set expectations and use mocks in fixture-context
    (waterContainerMock.isOverfull _).expects().returning(true)

    // and define helper functions
    def complexLogic() {
        coffeeMachine.powerOff()
        // ...
    }
  }

  "CoffeeMachine" should {
     "not turn on the heater when the water container is empty" in new MockContext {
         val heaterMock = mock[Heater]
         val waterContainerMock = mock[WaterContainer]
         val coffeeMachine = new CoffeeMachine(waterContainerMock, heaterMock)
         (waterContainerMock.isEmpty _).expects().returning(true)
         // ...
     }

     "not turn on the heater when the water container is overfull" in new OverfullWaterContainerTest {
         // ...
         complexLogic()
     }
  }
}

Isolated test cases

Using Isolated tests cases is a clean and simple way to share mocks and fixtures across all test cases. This technique is recommended when all test cases use the same or very similar fixtures.

IsolatedMockFactory can be mixed into a Specs2 Specification to provide mocking support. Please note that this causes the whole suite to run in isolated mode:

class IsolatedCoffeeMachineTest extends Specification with IsolatedMockFactory {
  
  // shared objects
  val waterContainerMock = mock[WaterContainer]
  val heaterMock = mock[Heater]
  val coffeeMachine = new CoffeeMachine(waterContainerMock, heaterMock)

  // you can set common expectations in suite scope
  (heaterMock.isReady _).expects().returning(true)

  // test setup
  coffeeMachine.powerOn()

  "CoffeeMachine" should {
      "not turn on the heater when the water container is empty" in {
          coffeeMachine.isOn must_== true
          // ...
          coffeeMachine.powerOff()
          coffeeMachine.isOn must_== false
      }

      "not turn on the heater when the water container is overfull" in {
          // each test case uses separate, fresh Suite so the coffee machine is turned on
          coffeeMachine.isOn must_== true
          // ...
      }
  }
}