1
votes

I am using a trait definition with various concrete derivatives and implicit to inject dependancies into objects and also to mock out parts of the system when unit testing. The problem is that when a mocked version of a type is used as an implicit declaration, it is not matched by scala in the consuming object.

Here is a simplified version of my setup. Is there a way to make Test1 work using a mock. Test2 works fine but it hard to maintain and requires too much setup.

A model:

case class User (first: String, last: String, enabled: Boolean)

Component Definition:

trait DataProviderComponent {
  def find[T](id: Int): Try[T]
  def update[T](data: T): Try[T]
}

One of the concrete component implementations:

class DbProvider extends DataProviderComponent {
  override def find[T](id: Int): Try[T] = { ... }
  override def update[T](data: T): Try[T] = { ... }
}

Implicit usage of component impl somewhere in system:

implicit val provider = new DbProvider()

class UserRepsitory(implicit provider: DataProviderComponent) {
  def userEnabled(id: Int): Boolean = {
    val user = provider.find[User](id)
    user.isSuccess && user.get.enabled
  }
}

Unit Test1, trying to mock out provider in order to isolate repository test. This does not work, the repository class does not match the implicit even though it is based on DataProviderComponent:

class UserRepository$Test1 extends FunSuite with Matchers with MockFactory {
  test("invalid data request should return false") {
    implicit val  mockProvider = mock[DataProviderComponent]
    (mockProvider.find[User] _).expects(13).returns(Failure[User](new Exception("Failed!")))

    val repo = new UserRepsitory()
    repo.userEnabled(13) should be (false)
  }
}

This version does work but is hard to maintain and requires more code:

class UserRepository$Test2 extends FunSuite with Matchers with MockFactory {
  test("invalid data request should return false") {
    class FakeProvider extends DataProviderComponent {
      override def find[T](id: Int): Try[T] = ???
      override def update[T](data: T): Try[T] = Failure[T](new Exception("Failed!"))
    }

    implicit val provider = new FakeProvider()
    val repo = new UserRepsitory()
    repo.userEnabled(13) should be (false)
  }
}

Is there a way to use a mocked type as an injected implicit - or is there another scala-thonic a pattern I should use to solve this problem?

1

1 Answers

2
votes

This code successfully compiles and runs for me

scala: 2.10.4

scalaTest: 2.1.0-RC2

scalaMock: 3.1.RC1

import org.scalamock.scalatest.MockFactory
import org.scalatest.{FunSuite, Matchers}

import scala.util.{Failure, Try}

case class User(first: String, last: String, enabled: Boolean)

trait DataProviderComponent {
  def find[T](id: Int): Try[T]

  def update[T](data: T): Try[T]
}

class DbProvider extends DataProviderComponent {
  override def find[T](id: Int): Try[T] = {
    ???
  }

  override def update[T](data: T): Try[T] = {
    ???
  }
}

class UserRepository(implicit provider: DataProviderComponent) {
  def userEnabled(id: Int): Boolean = {
    val user = provider.find[User](id)
    user.isSuccess && user.get.enabled
  }
}

class UserRepositoryTest extends FunSuite with Matchers with MockFactory {
  test("invalid data request should return false") {
    implicit val mockProvider: DataProviderComponent = mock[DataProviderComponent]
    (mockProvider.find[User] _).expects(13).returns(Failure[User](new Exception("Failed!")))

    val repo = new UserRepository()
    repo.userEnabled(13) should be(false)
  }
}