2

I'd like some help sorting out this scenario. I have an Akka actor where I want to inject a dependency, in this case RemoteFetcher, which I would also like mock in my tests. Like so:

main/src/scala/mypackage/Services.scala

package mypackage
import RemoteFetcherFileSystem._

trait RemoteFetcher {
  def fetch( path:String ): Future[Stream[String]]
}

class MyRemoteResourceActor extends Actor with ActorLogging {
  def fetchRemote( path:String ) = implicitly[RemoteFetcher].fetch( path )
  def receive = {
     case FetchRemoteResource( path ) => fetchRemote( path ).map( _.foreach( sender ! _ ) )
  }
}

For this to work I have an implicit object that I import into the file above. Would look something like this:

implicit object RemoteFetcherFileSystem extends RemoteFetcher {
   def fetchRemote( path:String ) = Future[Stream[String]] { ... reading from file system ... }
}

Now in my tests I have TestActor from the akka-testkit. Here I want to instead import my mock dependency:

implicit object RemoteFetcherMock extends RemoteFetcher {
   def fetchRemote( path:String ) = Future[Stream[String]] { ... mock implementation ... }
}

My problem is that to compile Services.scala I need to import the implicit object. But how do I go about to shadow/override this in my test-files. The reason I'm not using implicit arguments is that I want to avoid having to modify all my actors constructor arguments.

I when looking around and reading up on the type class dependency injection pattern and I get it to work according to the tutorials, but I don't get it to work when I want to test and override like in my example.

Magnus
  • 3,691
  • 5
  • 27
  • 35

2 Answers2

1

I'm not sure how to do it with implicits, but typically one could inject instead like so:

trait RemoteFetcherComponent {
  def remoteFetcher: RemoteFetcher
  trait RemoteFetcher {
    def fetch(path: String): Future[Stream[String]]
  }
}

trait RemoteFetcherFileSystemComponent extends RemoteFetcherComponent {
   val remoteFetcher = RemoteFetcherFileSystem
   object RemoteFetcherFileSystem extends RemoteFetcher {
     def fetch(path: String): Future[Stream[String]] = ???
   }
}

class MyRemoteResourceActor extends Actor with ActorLogging with RemoteFetcherFileSystemComponent {
  def fetchRemote(path: String) = remoteFetcher.fetch(path)
  def receive = {
     case FetchRemoteResource(path) => fetchRemote(path).map( _.foreach(sender ! _))
  }
}

val myRemoteResourceActor = new MyRemoteResourceActor()

And then a test value would be defined like so:

trait RemoteFetcherMockComponent extends RemoteFetcherComponent {
  def remoteFetcher = RemoteFetcherMock
  object RemoteFetcherMock extends RemoteFetcher {
    def fetch(path: String): Future[Stream[String]] = ???
  }
}

val myMockedResourceActor = new MyRemoteResourceActor with RemoteFetcherMockComponent {
  override val remoteFetcher = super[RemoteFetcherMockComponent].remoteFetcher
}

The reason you are having an issue with implicits is because the way you're using it is no different from simply using def fetchRemote(path: String) = RemoteFetcherFileSystem.fetch(path). With the import, you've defined the implementation, rather than allowed it to be injected later.

Alex DiCarlo
  • 4,851
  • 18
  • 34
  • So I guess you're suggesting to use the cake pattern here? Well it is worth a try. I got the impression in the tutorials I read that it was very verbose but it doesn't look too bad. – Magnus Jan 15 '13 at 01:53
  • I added another answer with a way to do it with implicits. Maybe not exactly how you want it, or the best way, but it's definitely a starting point. – Alex DiCarlo Jan 15 '13 at 02:10
  • I think you meant to have the actor extend RemoteFetcherFileSystemComponent? – Magnus Jan 15 '13 at 02:15
  • No, just the trait `RemoteFetcherComponent`. Then when you instantiate it you mixin `RemoteFetcherFileSystemComponent`, see the `val` in my example. – Alex DiCarlo Jan 15 '13 at 02:20
  • Hmmm... not sure this will work for my use case after all (at least as I defined it above). Since I don't instanciate the actor myself. Then again I could just pass constructor arguments. I'll have to take another look tomorrow, I guess I might just as well pass arguments to the constructor. – Magnus Jan 15 '13 at 02:23
  • I updated my post with the modification you suggested, although I doubt it's the best way to go about this, the cake pattern here seems to be a bit of overengineering. – Alex DiCarlo Jan 15 '13 at 02:27
  • As for the RemoteFetcherComponent. Don't I have to convert the class into a trait then? class MyRemoteResourceActor -> trait MyRemoteResourceActor, as otherwise I have to implement the remoteFetcher and fetch methods in my class. Which I want to do in the component implementations, correct? – Magnus Jan 15 '13 at 02:29
0

You could also change the implicitly to an implicit parameter:

trait RemoteFetcher {
  def fetch(path: String): Future[Stream[String]]
}

object RemoteFetcher {
   implicit val fetcher = RemoteFetcherFileSystem
}

class MyRemoteResourceActor extends Actor with ActorLogging {
  def fetchRemote(path: String)(implicit remoteFetcher: RemoteFetcher) = remoteFetcher.fetch(path)
  def receive = {
     case FetchRemoteResource(path) => fetchRemote(path).map( _.foreach(sender ! _))
  }
}

Then you could override the implicit that is resolved in the companion object of RemoteFetcher by simply importing RemoteFetcherMock.

See this post for more information about implicit parameter resolution precedence rules.

Community
  • 1
  • 1
Alex DiCarlo
  • 4,851
  • 18
  • 34
  • I tried that. But for some reason it did not really work out for me as I got implicit ambiguity collisions (maybe it was the REPL playing tricks on me). I guess that is an alternative as well. I think I will go with the cake pattern in the end. – Magnus Jan 15 '13 at 02:14