Given the following FooService:
scala> trait FooService {
| def go: Int
| }
defined trait FooService
There's also a MainService, which represents a main method.
scala> trait MainService extends FooService {
| def f = go + 42
| }
defined trait MainService
FooService can have a fake (for testing) and a real implementation (hits DB, for example):
scala> object FakeService extends FooService {
| def go = 10
| }
defined object FakeService
scala> object RealService extends FooService {
| def go = 55 // in reality, let's say it hit the DB and got a value
| }
defined object RealService
It seems to me that adding a "runner" class/trait, i.e. running sbt run would result in that class's execution, would be feasible. It would look like:
scala> class Main extends MainService {
| override def go = RealService.go
| }
defined class Main
And I could define a test too:
scala> class Test extends MainService {
| override def go = FakeService.go
| }
defined class Test
I'm not so sure that this is the idiomatic way of defining a real versus test MainService. Please let me know.
You can use the popular cake pattern which is also known as a "Scala way" of doing dependency injection.
Jon did a great blog post about this with a walkthrough (he listed also some alternatives).
Firstly, the trait for the FooService:
trait FooServiceComponent {
val fooService: FooService
trait FooService {
def go: Int
}
}
That's saying, we need two things: 1. the actual object, and 2. its definition/implementation. Both namespaced together. Nice. Here are the Fake and Real versions:
trait FakeService extends FooServiceComponent {
class FakeService extends FooService {
def go = 10
}
}
trait RealService extends FooServiceComponent {
class RealService extends FooService {
def go = 55
}
}
Now, for the MainService:
trait MainServiceComponent { this: FooServiceComponent =>
val mainService: MainService
class MainService extends FooService {
def f = go + 42
def go = fooService.go // using fooService
}
}
Note the self-typing this: FooServiceComponent which is a Scala way of saying that MainServiceComponent has a dependency on FooServiceComponent. If you try to instantiate MainServiceComponent without mixing in any FooServiceComponent then you will get a compile-time error. Nice. :)
Now, let's create the Test and Main objects with different traits:
object Test extends MainServiceComponent with FakeService {
val mainService = new MainService()
val fooService = new FakeService()
}
object Main extends MainServiceComponent with RealService {
val mainService = new MainService()
val fooService = new RealService()
}
Note that because of namespacing, FakeService can't be accessed in Main because it's not mixed in. Nice. :) Note also that you delay any instantiation of class until this point, which is convenient in that you can easily use a registry or mocking library to substitute them all in one place.
Results:
println(Test.mainService.f) // -> 52
println(Main.mainService.f) // -> 97
I hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With