scalamockitoscalatest

scala How can I make a test lenient with idiomatic mockito


Here is the code being tested:

import concurrent.{ExecutionContext, Future}

trait Backend {
  def verifyUser(): Future[Unit]
  def accounts(): Future[String]
}

class Service(backend: Backend) {
  private implicit val executor: ExecutionContext = ExecutionContext.global
  def accounts(): Future[String] =
    (for (_ <- backend.verifyUser(); accs <- backend.accounts()) yield accs)
    .recover { case ex => s"failed: ${ex.getMessage}" }
}

and the test

import org.scalatest._
import matchers.should
import flatspec.AsyncFlatSpec
import org.mockito.scalatest.AsyncIdiomaticMockito

class ServiceTest extends AsyncFlatSpec with AsyncIdiomaticMockito with should.Matchers {
  private trait Fixture {
    lazy val backend: Backend = mock[Backend]
    lazy val service: Service = new Service(backend)
  }

  it should "return the reason of a failure" in {
    val fixture = new Fixture {}
    import fixture._
    backend.verifyUser() returns Future.failed(new RuntimeException("verifyUser"))
    backend.accounts()   returns Future.failed(new RuntimeException("accounts"))
    service.accounts() map { result =>
      result should startWith ("failed: ")
    }
  }
}

The result is

Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
  1. -> at ServiceTest.$anonfun$new$1(StrictStubbingTest.scala:25)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
org.mockito.exceptions.misusing.UnnecessaryStubbingException: 

How can I make the test working leaving both stubs? How can I enable 'lenient' strictness as suggested just for that very test case, not for the whole test class?


Solution

  • My teammate gave such a solution:

    class ServiceTest extends AsyncFlatSpec with AsyncIdiomaticMockito with should.Matchers {
      private trait Fixture {
        lazy val backend: Backend = mock[Backend]
        lazy val service: Service = new Service(backend)
      }
    
      it should "return the reason of a failure" in {
        val fixture = new Fixture {
          override lazy val backend: Backend = mock[Backend](withSettings.lenient())
        }
        import fixture._
        backend.verifyUser() returns Future.failed(new RuntimeException("verifyUser"))
        backend.accounts()   returns Future.failed(new RuntimeException("accounts"))
        service.accounts() map { result =>
          result should startWith ("failed: ")
        }
      }
    }
    

    that is to make a lenient mock of Backend.