Scala: Mocking and the Cake Pattern

2020-05-21 04:49发布

问题:

I've been trying to adopt the Cake Pattern but I'm having difficulties adapting to this programming styles, especially where unit tests are concerned.

Lets assume that I have the following business objects:

trait Vet {
  def vaccinate(pet: Pet)
}

trait PetStore { this: Vet =>
  def sell(pet: Pet) {
    vaccinate(pet)
    // do some other stuff
  }
}

Now, I'd like to test PetStore while mocking out the functions from Vet. If I was using composition, I was creating a mock[Vet] and passing it to the PetStore constructor, then programming the mock like we do in the Java world. However, I can't find any reference to how people do this with the cake pattern.

One possible solution would be to implement vaccinate() on each test case according to the expected usage, but this then doesn't allow me to verify that the mocks were called properly, doesn't allow me to use matchers, etc.

So - how are people using Cake Pattern with mock objects?

回答1:

I started using the cake pattern after I read this blog post: https://github.com/precog/staticsite/blob/master/contents/blog/Existential-Types-FTW/index.md The approach is different from most Cake Pattern posts in that existential-types are used instead of self-types.

I have been using this pattern for a few months and it seems to work out well as I can specify a mock when I want to. It does have more a dependency injection feel to it, but it has all the benefits you get of having your code in traits.

My bastardized version of your problem using existential-types would be something like this:

case class Pet(val name: String)
trait ConfigComponent {
  type Config
  def config: Config
}

trait Vet {
  def vaccinate(pet: Pet) = {println ("Vaccinate:" + pet)}
}

trait PetStoreConfig {
  val vet: Vet
}
trait PetStore extends ConfigComponent {

    type Config <: PetStoreConfig

    def sell(pet: Pet) {
      config.vet.vaccinate(pet)
      // do some other stuff
    }
}

You can put it all together in your app

class MyApp extends PetStore with PetStoreConfig {

  type Config = MyApp
  def config = this  

  val vet = new Vet{}
  sell(new Pet("Fido"))

}

scala> new MyApp
Vaccinate:Pet(Fido)
res0: MyApp = MyApp@668dd96c

And you can test the components individually by creating an instance of VetLike and also creating a mock of VetLike an using it your PetStore test.

//Test VetLike Behavior
scala> val vet = new Vet{}
scala> vet.vaccinate(new Pet("Fido"))
Vaccinate:Pet(Fido)


//Test Petstore Behavior

class VetMock extends Vet {
   override def vaccinate(pet: Pet) = println("MOCKED")
}

class PetStoreTest extends PetStore with PetStoreConfig {
   type Config = PetStoreTest
   def config = this

   val vet = new VetMock
   val fido = new Pet("Fido")
   sell(fido)
}

scala> new PetStoreTest
MOCKED


回答2:

It's a good question. We came to the conclusion it can't be done, at least not quite the same way we're used to. It's possible to use stubs instead of mocks and mix the stubs in cake-wise. But this is more work than using mocks.

We have two Scala teams and one team adopted the cake pattern, using stubs instead of mocks, whilst the other team stuck to classes and dependency injection. Now I've tried both, I prefer DI with mocks due to it being simpler to test. And arguably simpler to read too.



回答3:

I have found a way to use Scalamock with Scalatest for the purpose of unit testing 'Cake Pattern' modules.

At first, I had many problems (including this one), but I believe the solution I present below is acceptable. If you have any concerns, please let me know.

This is how I would design your example:

trait VetModule {
  def vet: Vet
  trait Vet {
    def vaccinate(pet: Pet)
  }
}

trait PetStoreModule {
  self: VetModule =>
  def sell(pet: Pet)
}

trait PetStoreModuleImpl extends PetStoreModule {
  self: VetModule =>
  def sell(pet: Pet) {
    vet.vaccinate(pet)
    // do some other stuff
  }
}

The tests are then defined as following:

class TestPetstore extends FlatSpec with ShouldMatchers with MockFactory {

  trait PetstoreBehavior extends PetStoreModule with VetModule {

    object MockWrapper {
      var vet: Vet = null
    }

    def fixture = {
      val v = mock[Vet]
      MockWrapper.vet = v
      v
    }

    def t1 {
      val vet = fixture
      val p = Pet("Fido")
      (vet.vaccinate _).expects(p)
      sell(p)
    }

    def vet: Vet = MockWrapper.vet
  }

  val somePetStoreImpl = new PetstoreBehavior with PetStoreModuleImpl
  "The PetStore" should "vaccinate an animal before selling" in somePetStoreImpl.t1
}

Using this setup, you have the 'disadvantage' that you have to call val vet = fixture in every test you write. On the other hand, one can easily create another 'implementation' of the test, e.g.,

val someOtherPetStoreImpl = new PetstoreBehavior with PetStoreModuleOtherImpl


回答4:

Although this is an old question, I'm adding my answer for future readers. I believe this SO post - How to use mocks with the Cake Pattern - asks and answers the same thing.

I successfully followed the answer given by Vladimir Matveev (which was the top answer at the time of writing



标签: scala mocking