Hi,
We are in the midst of deciding how to mock dependencies for our testing structure, and wanted to get your advise / opinion about how to proceed. Just to start with a very basic /pseudo code example (which is very typical), let's say you have an MVC APP, and for data access, you have a DB object that abstracts data access with functions like get/save/update/etc as:
object DB {
val realDB = MySQL
def get(id: String) = { realDB.get(id) }
....
}
and you have a model class that is something like:
class User(id: String, name: String) extends Model {
def getUserName(id: String) = { DB.get(id).get("name") } // some trivial code like this
}
Now, during prod, we would like to use MySQL, but in our tests, we would prefer using an in memory DB or even a mock DB that throws exceptions as requested, etc.
As far as I can see the options are:
1. Cake Pattern: Although it looks great, there are two major concerns for me:
- The higher level code needs to know too much about the underlying library implementation details. For example, the model (or any consumer of a lib) should not know about where the data is coming from, all it needs to know is that there is some kind of a store somewhere that can persist your data. But, in the case of cake pattern, from the bottom to all the way up, you need to know what kind of Traits you are using, e.g. use MySQL + RedisCache and a read-through cache impl, etc.
- If your level of depth goes deeper, things start getting too complicated. For example, in a real world implementation of the example above, starting from the bottom : you have a DB driver, DB implementation, a central cache server (driver + impl) and a model and a controller (and many more services). Basically, at every level you need to specify what you are going to use, so your controller will be something like with User with DB with MySQL with Redis with ReadThrough, etc so that you can replace them with Mocks when you need to. And, as the depth and complexity increases, things start getting really messy.
2. Implicit parameter passing:
- Awesome, but similar to cake pattern, things start getting really complicated as the level of depth increases, and every intermediary should be aware of implicits and pass things along. Any new service / modification will require intermediaries to change, etc.
3. Factory / Service Locator patterns:
- I don't like these personally. But, apart from personal dislike, the main problem is that your service locator needs to be aware of the env, and give you the right impl at runtime, which will slow down your prod code, mainly because almost every call will have to go through these…
4. Static Setup approach:
- For example, in your DB object you will have something like "def setupRealDB(inRealDB) { realDB = inRealDB }. Basically, everything will default to prod services in code, and for testing environment, just before starting up tests, a static initiator will call all required setups with the right stuff and replace them as required
- Ugly as hell, but easiest to implement
5. Scala Guice:
- I consider DI with this approach as dark magic;) But, it looks like this is the most practical and probably unevitable approach? There are a few libraries available for running Guice on Scala (and would appreciate if you can tell what you recommend).
And, as I mentioned before, this example is the simplest form. In real world, even basic data access involves various levels of libs (including serializers, drivers, high level abstraction level, caching, etc.).
What is your take on this? Any comment at this point is most appreciated, because we are quite confused about how we should proceed.
Thanks a ton.
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
case class User(id: String, name: String) extends Model {
def save() {
DB.save...
}
}
Object User {
def getUserByID(id: String) = {
DB.get ....
}
}
How do you implicitly pass the DB here into companion object? Is that even possible *WITHOUT* creating a UserImpl trait and having the companion object extend that trait?
And, one more question that I have with implicit constructor injection is that, how do you handle multiple services? Let's say you have 10 services that you use and models use different combinations of them. Do you create a single service injector that defines all of them as implicit and pass this around? Or do you create different injectors (one per service or a few combinations that are used most together)?
Thanks...