Testing traits

Testing is a really important part of software development. It ensures that changes to a certain piece of code do not end up producing errors either in the methods that were changed, or somewhere else.

There are different testing frameworks that one can use, and it really is a matter of personal preference. In this book, we have used ScalaTest (http://www.scalatest.org), as this is the one I use in my projects; it is understandable, readable, and easy to use.

In some cases, if a trait is mixed into a class, we could end up testing the class. However, we might want to test only a specific trait. It does not make much sense to test a trait that doesn't have all its methods implemented, so here we will look into the ones that have their code written (mixins). Also, the unit tests that we will show here are quite simple but they are just for illustration purposes. We will be looking into more complex and meaningful tests in the following chapters of this book.

Using a class

Let's have a look at how DoubledMultiplierIdentity, which we saw previously, would be tested. One would try to simply mix the trait into a test class and test the methods:

class DoubledMultiplierIdentityTest extends FlatSpec with ShouldMatchers with DoubledMultiplierIdentity

This, however, won't compile and will lead to the following error:

Error:(5, 79) illegal inheritance; superclass FlatSpec 
 is not a subclass of the superclass MultiplierIdentity 
 of the mixin trait DoubledMultiplierIdentity 
class DoubledMultiplierIdentityTest extends FlatSpec with ShouldMatchers with DoubledMultiplierIdentity { 
                                                                              ^

We already talked about this before and the fact that a trait can only be mixed in a class that has the same super class as itself. This means that in order to test the trait, we should create a dummy class inside our test class and then use it:

package com.ivan.nikolov.linearization 

import org.scalatest.{ShouldMatchers, FlatSpec} 

class DoubledMultiplierIdentityTest extends FlatSpec with ShouldMatchers { 
 
  class DoubledMultiplierIdentityClass extends DoubledMultiplierIdentity 
  
  val instance = new DoubledMultiplierIdentityClass 
  
  "identity" should "return 2 * 1" in { 
    instance.identity should equal(2) 
  } 
}

Mixing the trait in

We can test a trait by mixing it in. There are a few places where we can do this: into a test class or into separate test cases.

Mixing into the test class

Mixing in a trait into a test class is only possible if the trait does not extend any other class explicitly, hence the super class of the trait and the test will be the same. Other than this, everything else is absolutely the same as done previously.

Let's test the A trait from earlier in this chapter, which says hello. We've also added an extra pass method, and now the trait looks like the following:

trait A { 
  def hello(): String = "Hello, I am trait A!" 
  def pass(a: Int): String = s"Trait A said: 'You passed $a.'" 
}

Here, is how the unit test will look like:

package com.ivan.nikolov.composition 

import org.scalatest.{ShouldMatchers, FlatSpec} 

class TraitATest extends FlatSpec with ShouldMatchers with A { 

  "hello" should "greet properly." in { 
    hello() should equal("Hello, I am trait A!") 
  } 
  
  "pass" should "return the right string with the number." in { 
    pass(10) should equal("Trait A said: 'You passed 10.'") 
  } 
  
  it should "be correct also for negative values." in { 
    pass(-10) should equal("Trait A said: 'You passed -10.'") 
  } 
} 
Mixing into the test cases

We can also mix traits into the inpidual test cases separately. This could allow us to apply customizations specific to those test cases only. The following is just a different representation of the preceding unit test:

package com.ivan.nikolov.composition

import org.scalatest.{ShouldMatchers, FlatSpec}

class TraitACaseScopeTest extends FlatSpec with ShouldMatchers {

  "hello" should "greet properly." in new A {
    hello() should equal("Hello, I am trait A!")
  }
  "pass" should "return the right string with the number." in new A {
    pass(10) should equal("Trait A said: 'You passed 10.'")
  }
  it should "be correct also for negative values." in new A {
    pass(-10) should equal("Trait A said: 'You passed -10.'")
  }
}

As you can see in the preceding code, the test cases are identical to the previous ones. They, however, inpidually mix A in. This would allow us to apply different customizations in the cases where a trait requires an implementation of a method or a variable initialization. This way we can also focus specifically on the trait being tested, rather than creating actual instances of it.

Running the tests

After the tests are written, it is useful to run them in order to see whether everything works as expected. Just run the following command from the root of your project and it will execute all the tests:

mvn clean test

If you want, the project can be converted to SBT instead of Maven and then the tests can be triggered using an sbt test command.