Mocking Classes
Learn some best practices for mocking classes.
We'll cover the following...
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))
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 ...