Effective testing with Spek

Jarosław Michalik

  • Android developer
  • IoT and telemedicine
  • Organizer @ SFI IT Academic Festival

Unit testing in JVM

  • jUnit
  • KotlinTest
  • Spock

jUnit

                    
public class CalculatorTest {
  @Test
  public void evaluatesExpression() {
    Calculator calculator = new Calculator();
    int sum = calculator.evaluate("1+2+3");
    assertEquals(6, sum);
  }
}
                    
                

Mockito

                    
import static org.mockito.Mockito.*;

 //mock creation
 List mockedList = mock(List.class);

 //using mock object
 mockedList.add("one");
 mockedList.clear();

 //verification
 verify(mockedList).add("one");
 verify(mockedList).clear();

                    
                

BDDMockito

                    
given(phoneBookRepository.contains(momContactName))
  .willReturn(false);

phoneBookService.register(xContactName, "");

then(phoneBookRepository)
  .should(never())
  .insert(momContactName, momPhoneNumber);

                    
                

jUnit has some issues

code repetition

                    
class DistanceConverterTest{
    @Test
    fun printOver50KmWhenDistanceIsGreaterThan50000m(){
        val distanceConverter = DistanceConverter()
        val convert = distanceConverter.convert(61000.0)
        assert(convert.contentEquals(">50km"))
    }

    @Test
    fun printExactValueInMetersWhenDistanceIsLesserThan1000m(){
        val distanceConverter = DistanceConverter()
        val convert = distanceConverter.convert(987.45)
        assert(convert.contentEquals("987m"))
    }

    @Test
    fun printValueInKilometersWhenDistanceIsBelow50000m(){
        val distanceConverter = DistanceConverter()
        val convert = distanceConverter.convert(37432.123)
        assert(convert.contentEquals("37.4km"))
    }
}
                    
                

...but can be parametrized by annotations

                    
@Test
fun printExactValueInMetersWhenDistanceIsLesserThan1000m(){}
                    
                

...or with underscore

                    
@Test
fun print_exact_value_in_meters_when_distance_lesser_than_1000m(){}
                    
                

...or in quotes (Kotlin, Groovy)

                        
@Test
fun 'print exact value in meters when distance lesser than 1000km'(){}
                        
                    

Why I decided to try Spek?

  • another city app
  • needed to implement distance formatting based on unit system, language, etc.
  • business guys gave us very specific requirements how distance should be formatted in each case
value [m] display
753 753m
1337 1.34km
15888 15.9km
31270 31km
51000 >50km

We want our tests to be

  • clean
  • meaningful
  • self-describing

Let's write specification!

annotations

DSL

describe

                            
fun SpecBody.describe(description: String, body: SpecBody.() -> Unit) {
    group("describe $description", body = body)
}
                            

                    
describe("distance converter"){
    val converter = DistanceConverter()
}
                    
                        

specify behavior in given context

                    
context("SI units"){
    describe("distance converter"){
        val converter = DistanceConverter()
    }
}
                    
                

finally, make some assertions

                    
context("SI units"){
    describe("distance converter"){
        val converter = DistanceConverter()
            it("should convert 32 433 meters to 32.4 km"){
                assertTrue(
                        converter.convert(32433)
                            .contentEquals("32.4km")
                )
            }
    }
}
                    
                

another sample

                    
class DistanceConverterSpecificiation : Spek({ //this: Spek
    describe("distance converter") { //this: SpekBody
        val converter = DistanceConverter
        on("distance 61888.123m") { //this: ActionBody
            val testDistance = 61888.123
            it("should return >50km") { //this: TestBody
                val output = converter.convert(testDistance)
                assertTrue(output.contentEquals(">50km"))
            }
        }
    }
})
                    
                

IntelIJ / AS plugin

it creates test case
                            
fun TestContainer.it(description: String, body: TestBody.() -> Unit) {
    test("it $description", body = body)
}
                            
                
on creates ActionBody group
                    
fun SpecBody.on(description: String, body: ActionBody.() -> Unit) {
    action("on $description", body = body)
}
                    
                
describe, given, context creates SpekBody group
                    
fun SpecBody.given(description: String, body: SpecBody.() -> Unit) {
    group("given $description", body = body)
}
                    
                

Action!

Summary

test + specification -> system documentation

write specifications - understand product you're building better

find errors easier

create groups - test code is more readable

don't repeat yourself - create extensions for common cases

... and create parametrized test with forEach{}

integrate with jUnit and Mockito

write tests in Kotlin

Q&A

Jarosław Michalik

rozkmin.me
github.com/rozkminiacz