Dependency injection for `trait`
Considering the following FooService
:
scala> trait FooService {
| def go: Int
| }
defined trait FooService
There also exists MainService
which is the main method.
scala> trait MainService extends FooService {
| def f = go + 42
| }
defined trait MainService
FooService
may have a fake (for testing) and a real implementation (for example, a database):
scala> object FakeService extends FooService {
| def go = 10
| }
defined object FakeService
scala> object RealService extends FooService {
| def go = 55 // in reality, let say it hit the DB and got a value
| }
defined object RealService
It seems to me that adding a class / trait "runner", i.e. execution sbt run
will result in the execution of this class. It will look like this:
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 sure if this is the idiomatic way of defining a real test case MainService
. Please let me know.
source to share
You can use the popular cake pattern also known as the "Scala way" for dependency injection.
John made a great blog post about this with a step by step guide (he also lists some alternatives).
First, a trait for FooService
:
trait FooServiceComponent {
val fooService: FooService
trait FooService {
def go: Int
}
}
In this statement, we need two things: 1. the actual object and 2. its definition / implementation. Both named together. Nice. Here are the versions Fake
and Real
:
trait FakeService extends FooServiceComponent {
class FakeService extends FooService {
def go = 10
}
}
trait RealService extends FooServiceComponent {
class RealService extends FooService {
def go = 55
}
}
Now, for 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-tuning this: FooServiceComponent
, which is Scala's way of saying it MainServiceComponent
has a dependency on FooServiceComponent
. If you try to instantiate MainServiceComponent
without mixing anywhere FooServiceComponent
, then you get a compile time error. Nice.:)
Now create objects Test
and Main
with different features:
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 due to the namespace, FakeService
it is not possible to access Main
it because it has not been shuffled. Nice. :) Note also that you are delaying any class creation until this point, which is handy in that you can easily use the registry or a mocking library to replace them all in one place.
Results:
println(Test.mainService.f) // -> 52
println(Main.mainService.f) // -> 97
Hope this helps.
source to share