Scala: Mocking and the Cake Pattern

后端 未结 4 894
梦如初夏
梦如初夏 2021-02-03 11:32

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

相关标签:
4条回答
  • 2021-02-03 11:59

    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.

    0 讨论(0)
  • 2021-02-03 12:09

    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
    
    0 讨论(0)
  • 2021-02-03 12:15

    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
    
    0 讨论(0)
  • 2021-02-03 12:16

    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

    0 讨论(0)
提交回复
热议问题