可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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