Testing Private Methods and Nested Properties
Learn how to make more fine-grained assertions targeting private and nested properties of a class.
We'll cover the following...
Now that we’ve discussed some best practices in ScalaTest, it’s time to explore more syntactic sugar. We’ll start by looking at how we can test nested properties and private methods.
Nested properties
The simplest way to check for nested properties is to rely on direct access. For example, assume we wanted to validate all the properties of a Course
.
package mdipirro.educative.io.effectiveunitandintegrationtestinginscala.matchers import mdipirro.educative.io.effectiveunitandintegrationtestinginscala.TestSuite import mdipirro.educative.io.effectiveunitandintegrationtestinginscala.model.v2.{Author, Educative, FreeCourse, Lesson} class EducativeSpec extends TestSuite: private val educative = Educative(Seq( FreeCourse("Scala for Beginners", Author("John", "Doe"), Set.empty[Lesson], Set.empty[String]), FreeCourse("Scala for Data Analysis", Author("Mary", "Jane"), Set.empty[Lesson], Set("Scala")), FreeCourse("Functional Kotlin", Author("Mary", "Jane"), Set.empty[Lesson], Set("kotlin")) )) "The Educative platform" `should` "return a valid course, if it exists" in { val c = educative.courseByName("Scala for Beginners").get c.title shouldBe "Scala for Beginners" c.author.firstName shouldBe "John" c.author.lastName shouldBe "Doe" c.lessons shouldBe empty c.tags shouldBe empty c.price shouldBe 0 }
Ignore for now the .get
invoked on the Option[Course]
returned by Educative#courseByName
(line 15). The example above asserts the expected value for each property of Course
. There are more idiomatic ways, such as pattern matching.
package mdipirro.educative.io.effectiveunitandintegrationtestinginscala.matchers import mdipirro.educative.io.effectiveunitandintegrationtestinginscala.TestSuite import mdipirro.educative.io.effectiveunitandintegrationtestinginscala.model.v2.{Author, Educative, FreeCourse, Lesson, PaidCourse} class EducativeSpec extends TestSuite: private val educative = Educative(Seq( FreeCourse("Scala for Beginners", Author("John", "Doe"), Set.empty[Lesson], Set.empty[String]), FreeCourse("Scala for Data Analysis", Author("Mary", "Jane"), Set.empty[Lesson], Set("Scala")), FreeCourse("Functional Kotlin", Author("Mary", "Jane"), Set.empty[Lesson], Set("kotlin")) )) "The Educative platform" `should` "return a valid course, if it exists" in { educative.courseByName("Scala for Beginners").get match case fc@FreeCourse(title, Author(firstName, lastName), lessons, tags) => title shouldBe "Scala for Beginners" firstName shouldBe "John" lastName shouldBe "Doe" lessons shouldBe empty tags shouldBe empty fc.price shouldBe 0 case PaidCourse(_, _, _, _) => fail("Expecting a FreeCourse, got a PaidCourse") }
Pattern matching usually makes the code clearer, but in this case, it adds some boilerplate. Not only do we have to explicitly fail
the PaidCourse
match, but we also have to give a name to the matched FreeCourse
(fc
in the example above) to check fc.price
. Furthermore, by destructuring Author
as well, we flatten all the properties because now it’s less clear that firstName
and lastName
...