Reputation: 177
I have a case class in scala with new object for another class like(for simplicity, it's not returning anything say):
case class A() extends Logging with Serializable {
def run(escid: String): Unit = {
new B("value").call()
}
}
How can I write unit test of run method such that I can stub call to do nothing.
I am using mockito-scala and couldn't find any example to mock constructor . Is there any way I can write scala test with mocking construcor.
Upvotes: 0
Views: 271
Reputation: 3581
From the provided example in the question
case class A() extends Logging with Serializable {
def run(escid: String): Unit = {
new B("value").call()
}
}
since mockito 3.4, you can use Mockito.mockConstruction
. An example of how to use it in your case would be
import org.mockito.MockedConstruction.{Context, MockInitializer}
import org.mockito.Mockito.mockConstruction
import org.mockito.scalatest.MockitoSugar
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import scala.util.{Failure, Success, Using}
class ScalaMainTest extends AnyFunSuite with Matchers with MockitoSugar {
class B(s: String) {
def call(): String = s
}
case class A() {
def run: String = new B("a").call() // returns "a"
}
object MyMockInitializer extends MockInitializer[B] { // overrides the constructor
override def prepare(mock: B, context: Context): Unit =
when(mock.call()).thenReturn("b") // returns "b"
}
test("mocked constructor") {
Using(mockConstruction(classOf[B], MyMockInitializer)) { _ => // context with the constructor overriden
A().run should be("b") // calling the method that depends on other class
} match {
case Failure(exception) => fail(exception)
case Success(value) => value
}
}
}
This seems like a design problem. Having a concrete class being created inside another class make things tightly coupled. Applying dependency injection will give you more flexibility and will let you create tests that are easier to setup and write.
In scala you have the primary constructor and the auxiliary constructor
// primary constructor that receives the parameters defined here
class MyClass(param1: Param1, param2: Param2, paramN: ParamN) {
// a field that its value depends on
val myField = {
// some logic that could depends on the values of
// the params received in the constructor
}
// secondary constructor that returns an instance using the primary constructor
def this(param1: Param1, param2: Param2) =
this(param1, param2, defaultValueN)
// secondary constructor that calls the above secondary constructor
def this(param1: Param1) =
this(param1, defaultValue2, defaultValueN)
def myMethod() = {
// some logic that could depends on the values of
// the params received in the constructor
}
}
then you can instance the class like this
val myClassPrimaryConstructor = new MyClass(param1, param2, paramN)
val myClassSecondaryConstructor1 = new MyClass(param1, param2)
val myClassSecondaryConstructor2 = new MyClass(param1)
Unless you have a really complex logic in myField
or myMethod
, just passing mocks as parameters in the constructor would be enough to validate the expected results based on the inputs.
You can mock constructors using PowerMock or Mockito since version 3.4
Mocking constructors or static methods is impossible using Mockito version 3.3 or lower. In such cases, a library like PowerMock provides additional capabilities that allow us to mock the behavior of constructors and orchestrate their interactions.
Also you should consider if it is possible to refactor your code before start mocking constructors
Generally speaking, some might say that when writing clean object-orientated code, we shouldn’t need to return a mock instance when creating an object. This could typically hint at a design issue or code smell in our application.
Why? First, a class depending on several concrete implementations could be tightly coupled, and second, this nearly always leads to code that is difficult to test. Ideally, a class should not be responsible for obtaining its dependencies, and if possible, they should be externally injected.
So, it’s always worth investigating if we can refactor our code to make it more testable. Of course, this isn’t always possible, and sometimes, we need to temporarily replace the behavior of a class after constructing it.
This might be particularly useful in several situations:
- Testing difficult-to-reach scenarios – particularly if our class under test has a complex object hierarchy
- Testing interactions with external libraries or frameworks
- Working with legacy code
Upvotes: 0