Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to mock / override dependencies / imports in Scala?

I have some code looking like this:

package org.samidarko.actors

import org.samidarko.helpers.Lib

class Monitoring extends Actor {

  override def receive: Receive = {
    case Tick =>
       Lib.sendNotification()
    }
}

Is there a way to mock/stub Lib from ScalaTest like with proxyquire for nodejs?

I read that I could use dependency injection but I would rather not do that

Is my only alternative is to pass my lib as class parameter?

class Monitoring(lib: Lib) extends Actor {

Any advice to make it more testable? Thanks

EDIT:

Xavier Guihot's answer is an interesting approach of the problem but I choose to change the code for testing purpose.

I'm passing the Lib as parameter and I'm mocking with mockito, it makes the code easier to test and to maintain than shadowing the scope.

like image 215
samidarko Avatar asked Mar 01 '18 03:03

samidarko


1 Answers

This answer only uses scalatest and doesn't impact the source code:

Basic solution:

Let's say you have this src class (the one you want to test and for which you want to mock the dependency):

package com.my.code

import com.lib.LibHelper

class MyClass() {
  def myFunction(): String = LibHelper.help()
}

and this library dependency (which you want to mock / override when testing MyClass):

package com.lib

object LibHelper {
  def help(): String = "hello world"
}

The idea is to create a class in your test folder which will override/shadow the library. The class will have the same name and the same package as the one you want to mock. In src/test/scala/com/external/lib, you can create LibHelper.scala which contains this code:

package com.lib

object LibHelper {
  def help(): String = "hello world - overriden"
}

And this way you can test your code the usual way:

package com.my.code

import org.scalatest.FunSuite

class MyClassTest extends FunSuite {
  test("my_test") {
    assert(new MyClass().myFunction() === "hello world - overriden")
  }
}

Improved solution which allows setting the behavior of the mock for each test:

Previous code is clear and simple but the mocked behavior of LibHelper is the same for all tests. And one might want to have a method of LibHelper produce different outputs. We can thus consider setting a mutable variable in the LibHelper and updating the variable before each test in order to set the desired behavior of LibHelper. (This only works if LibHelper is an object)

The shadowing LibHelper (the one in src/test/scala/com/external/lib) should be replaced with:

package com.lib

object LibHelper {

  var testName = "test_1"

  def help(): String =
    testName match {
      case "test_1" => "hello world - overriden - test 1"
      case "test_2" => "hello world - overriden - test 2"
    }
}

And the scalatest class should become:

package com.my.code

import com.lib.LibHelper

import org.scalatest.FunSuite

class MyClassTest extends FunSuite {
  test("test_1") {
    LibHelper.testName = "test_1"
    assert(new MyClass().myFunction() === "hello world - overriden - test 1")
  }
  test("test_2") {
    LibHelper.testName = "test_2"
    assert(new MyClass().myFunction() === "hello world - overriden - test 2")
  }
}

Very important precision, since we're using a global variable, it is compulsory to force scalatest to run test in sequence (not in parallel). The associated scalatest option (to be included in build.sbt) is:

parallelExecution in Test := false
like image 101
Xavier Guihot Avatar answered Sep 19 '22 14:09

Xavier Guihot