Mocking Classes

Learn some best practices for mocking classes.

Dependencies among different units should be modeled using traits as much as possible since it allows for a cleaner design and simplified unit testing. It’s generally better to mock a trait rather than a concrete class. However, there are situations where we might need to mock a concrete class. For example, this can happen a lot when dealing with legacy code that we can’t refactor.

Creating mocks for classes

If the class we want to mock is not declared final, the creation of the mocks looks exactly like what we did for traits.

package mdipirro.educative.io.effectiveunitandintegrationtestinginscala.mocks

import mdipirro.educative.io.effectiveunitandintegrationtestinginscala.TestSuite
import mdipirro.educative.io.effectiveunitandintegrationtestinginscala.model.v3.{Author, Course, Educative, FreeCourse, Lesson, PaidCourse}
import org.mockito.ArgumentMatchers.{any, anyString}
import org.mockito.Mockito.{atLeastOnce, never, spy, times, verify, when}
import org.scalatestplus.mockito.MockitoSugar

class MockitoClass extends TestSuite with MockitoSugar:
  "Publishing a course with no lessons" `should` "leave the course-base as is" in new Fixture:
    educative.publishCourse("Scala for Dummies")

    verify(scalaForDummies, times(1)).title
    verify(scalaForDummies, times(1)).lessons
    verify(scalaForDummies, never).published
    verify(scalaForDummies, never).copy()

  "Publishing a course with some lessons" `should` "update the course-base" in new Fixture:
    educative.publishCourse("Advanced Scala")

    verify(advancedScala, times(1)).title
    verify(advancedScala, times(1)).lessons
    verify(advancedScala, times(1)).published
    verify(advancedScala, atLeastOnce()).copy()

  trait Fixture:
    val scalaForDummies: Course =
      val courseMock = mock[FreeCourse]
      when(courseMock.title).thenReturn("Scala for Dummies")
      when(courseMock.lessons).thenReturn(Set.empty)
      when(courseMock.published).thenReturn(false)
      courseMock

    val advancedScala: Course =
      val courseMock = mock[PaidCourse]
      when(courseMock.title).thenReturn("Advanced Scala")
      when(courseMock.lessons).thenReturn(Set(Lesson("Introduction")))
      when(courseMock.published).thenReturn(false)
      when(courseMock.copy(published = true)).thenReturn(courseMock)
      courseMock

    val educative: Educative = Educative(Seq(scalaForDummies, advancedScala))
Using Mockito on Scala classes

We start getting into trouble when we want to mock final classes (including final case classes). While it’s true that you should mock as few classes (especially final classes) as possible, there are times when this is useful. For example, you might be dealing with legacy code you can’t refactor. Or maybe the class you’re working with has every right to be final, and your need for the mock is completely legitimate. By now, you should know that best practices often depend on the context and use cases, especially when it comes to testing. For example, assume PaidCourse is a ...