What Do We Need Mocks For?

Learn about the different types of test doubles and how we can use them.

Until now, we’ve been testing small components in isolation as much as possible. Unit testing is essentially about testing units of code (classes, objects, and so on) without any interaction with the rest of the codebase. The problem is that often our units need other units to work. In this lesson, we’re going to discuss a couple of testing styles to limit, as much as possible, interaction between the unit under test and other units.

State testing vs. behavior testing

When it comes to testing units together, we have two major ways to design our tests: state verification and behavior verification. To show their differences, let’s add a new feature to our small program: the possibility of publishing a course. A course can be published only if it has more than one lesson.

Press + to interact
sealed trait Course:
val title: String
val author: Author
val lessons: Set[Lesson]
val tags: Set[String]
val price: BigDecimal
val published: Boolean
require(lessons.nonEmpty || !published, "A course can be published only if it has at least one lesson")
def copy(
title: String = title,
author: Author = author,
lessons: Set[Lesson] = lessons,
tags: Set[String] = tags,
published: Boolean = published
): Course
case class FreeCourse(
override val title: String,
override val author: Author,
override val lessons: Set[Lesson],
override val tags: Set[String],
override val published: Boolean
) extends Course:
override val price: BigDecimal = ??? // as before
case class PaidCourse(
override val title: String,
override val author: Author,
override val lessons: Set[Lesson],
override val tags: Set[String],
override val published: Boolean
) extends Course:
override val price: BigDecimal = ??? // as before
class Educative(val courses: Seq[Course]):
// rest of Educative as before
def publishCourse(title: String): Educative = Educative(courses map { c =>
if c.title == title && c.lessons.nonEmpty && !c.published then c.copy(published = true)
else c
})

The implementation of the publishCourse method simply goes through the collection of courses, looking for one with the specified title. If such a Course exists and has at least one lesson, the course is replaced with one instance with the published flag set. Notice that publishing the same Course twice will not replace it the second time because the if condition makes sure the Course was not published already. Furthermore, to call copy, we have to explicitly define an abstract method with the same name in the Course trait.

State testing

The first approach, known as state verification, involves making assertions of the final state of the unit under test, making sure it’s what we expect.

package mdipirro.educative.io.effectiveunitandintegrationtestinginscala.mocks

import mdipirro.educative.io.effectiveunitandintegrationtestinginscala.TestSuite
import mdipirro.educative.io.effectiveunitandintegrationtestinginscala.model.v3.{Author, Educative, FreeCourse, Lesson, PaidCourse}
import org.scalatest.OptionValues

class StateVerification extends TestSuite with OptionValues:
  "Publishing a course with no lessons" `should` "leave the course-base as is" in new Fixture {
    educative
      .publishCourse("Scala for Dummies")
      .courseByName("Scala for Dummies").value.published shouldBe false
  }

  "Publishing a course with some lessons" `should` "update the course-base" in new Fixture {
    educative
      .publishCourse("Advanced Scala")
      .courseByName("Advanced Scala").value.published shouldBe true
  }

  trait Fixture:
    val educative: Educative = Educative(
      Seq(
        FreeCourse("Scala for Dummies", Author("Mary", "Jane"), Set.empty, Set.empty),
        PaidCourse("Advanced Scala", Author("Mary", "Jane"), Set(Lesson("Introduction")), Set.empty)
      )
    )
State verification

In the test suite above, we first invoke the Educative#publishCourse method (lines 10 and 16) on an instance of Educative (a data fixture). We then rely on the result of Educative#courseByName (chained with OptionsValues#value) ...

Get hands-on with 1400+ tech skills courses.