Slide 1

Slide 1 text

Ge#ng the most out of your next genera2on JVM tes2ng framework JUnit 5 Spock Benjamin Muschko

Slide 2

Slide 2 text

AUTOMATED ASCENT bmuschko bmuschko bmuschko.com About the speaker automatedascent.com

Slide 3

Slide 3 text

Which test framework do you use?

Slide 4

Slide 4 text

JUnit 5 in a nutshell Complete rewrite of JUnit Modular & extensible Forward & backward compa2ble

Slide 5

Slide 5 text

Far more than a version upgrade

Slide 6

Slide 6 text

JUnit Jupiter The programming and extension model

Slide 7

Slide 7 text

Spock Framework BDD tes2ng framework

Slide 8

Slide 8 text

Spock, short and sweet Tests are wriLen in Groovy Highly expressive, extensible JUnit 4-compa2ble with test runner

Slide 9

Slide 9 text

given when then defines the key ac2on known state or data verifies expected outcome Specifica2on of a test

Slide 10

Slide 10 text

Tests need to be expressive

Slide 11

Slide 11 text

import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class DescriptiveTest {
 private final ArithmeticOperation arithmeticOperation =↵ new ArithmeticOperation();
 
 @Test
 @DisplayName("can add two numbers")
 void canAdd() {
 assertEquals(3, arithmeticOperation.add(1, 2));
 }
 } Descrip2ve tests

Slide 12

Slide 12 text

Execu2ng tests in IntelliJ

Slide 13

Slide 13 text

import spock.lang.Specification
 import spock.lang.Subject
 
 class DescriptiveTest extends Specification {
 @Subject def arithmeticOperation =↵ new ArithmeticOperation()
 
 def "can add two numbers"() {
 when:
 def result = arithmeticOperation.add(1, 2) then: result == 3 
 }
 } Descrip2ve tests

Slide 14

Slide 14 text

Execu2ng tests in IntelliJ

Slide 15

Slide 15 text

Tests may require preparation

Slide 16

Slide 16 text

import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 
 public class FixtureSetupCleanup {
 private Path testFile;
 
 @BeforeEach
 void setup() throws IOException {
 testFile = Files.createTempFile("junit5", ".tmp");
 }
 
 @AfterEach
 void cleanup() {
 testFile.toFile().delete();
 }
 } Fixture set up and tear down

Slide 17

Slide 17 text

import spock.lang.Specification
 import spock.lang.Subject
 
 class FixtureSetupCleanup extends Specification {
 def testFile
 
 def setup() {
 testFile = Files.createTempFile("junit5", ".tmp")
 }
 
 def cleanup() {
 testFile.toFile().delete()
 }
 } Fixture set up and tear down

Slide 18

Slide 18 text

Putting tests to sleep

Slide 19

Slide 19 text

import org.junit.jupiter.api.Disabled;
 
 public class IgnoredTest {
 private final ArithmeticOperation arithmeticOperation =↵ new ArithmeticOperation();
 
 @Test
 @Disabled("for demonstration purposes")
 void canAdd() {
 assertEquals(3, arithmeticOperation.add(1, 2));
 }
 } Disabling tests

Slide 20

Slide 20 text

import spock.lang.Ignore
 
 class IgnoredTest extends Specification {
 @Subject def arithmeticOperation =↵ new ArithmeticOperation()
 
 @Ignore("for demonstration purposes")
 def canAdd() {
 expect:
 arithmeticOperation.add(1, 2) == 3
 }
 } Disabling tests

Slide 21

Slide 21 text

import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
 
 public class ConditionalExecutionTest {
 private final static String SYS_PROP_KEY = "junit5.test.enabled";
 private final static String SYS_PROP_TRUE_VALUE = "true";
 private final ArithmeticOperation arithmeticOperation =↵ new ArithmeticOperation();
 
 @EnabledIfSystemProperty(named = SYS_PROP_KEY,↵ matches = SYS_PROP_TRUE_VALUE)
 @Test
 void testOnlyOnSystemSystemProperty() {
 assertEquals(3, arithmeticOperation.add(1, 2));
 }
 } Built-in condi2onals

Slide 22

Slide 22 text

import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
 public class ConditionalExecutionTest {
 private final static String SYS_PROP_KEY = "junit5.test.enabled";
 private final static String SYS_PROP_TRUE_VALUE = "true";
 private final ArithmeticOperation arithmeticOperation =↵ new ArithmeticOperation();
 
 @Test
 void testOnlyOnSystemPropertySet() {
 assumeTrue(SYS_PROP_TRUE_VALUE↵ .equals(System.getProperty(SYS_PROP_KEY)));
 assertEquals(3, arithmeticOperation.add(1, 2));
 }
 } Alterna2ve condi2onal syntax

Slide 23

Slide 23 text

import org.junit.jupiter.api.extension.*;
 
 import static org.junit.jupiter.api.extension.ConditionEvaluationResult.*;
 
 public static class SystemPropertyConditionalExtension implements ExecutionCondition {
 @Override
 public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext↵
 context) {
 String sysPropValue = System.getProperty(SYS_PROP_KEY);
 boolean enabled = SYS_PROP_TRUE_VALUE.equals(sysPropValue);
 
 if (enabled) {
 return enabled(String.format("System property '%s' evaluates to true",↵
 SYS_PROP_KEY));
 }
 
 return disabled(String.format("System property '%s' evaluates to false”,↵
 SYS_PROP_KEY));
 }
 } Custom condi2onals

Slide 24

Slide 24 text

import org.junit.jupiter.api.extension.ExtendWith;
 
 public class ConditionalExecutionTest {
 private final ArithmeticOperation arithmeticOperation =↵ new ArithmeticOperation();
 
 @ExtendWith(SystemPropertyConditionalExtension.class)
 @Test
 void testOnlyOnSystemPropertySetByExtension() {
 assertEquals(3, arithmeticOperation.add(1, 2));
 }
 } Using custom condi2onals

Slide 25

Slide 25 text

import spock.lang.Requires
 
 class ConditionalExecutionTest extends Specification {
 private final static String SYS_PROP_KEY = "spock.test.enabled"
 private final static String SYS_PROP_TRUE_VALUE = "true"
 @Subject def arithmeticOperation = new ArithmeticOperation()
 
 @Requires({ SYS_PROP_TRUE_VALUE == sys[SYS_PROP_KEY] })
 def "can add"() {
 expect:
 arithmeticOperation.add(1, 2) == 3
 }
 } Condi2onal test execu2on

Slide 26

Slide 26 text

Expect things to misbehave

Slide 27

Slide 27 text

import static org.junit.jupiter.api.Assertions.assertThrows;
 
 public class ExpectedExceptionTest {
 private final FileReader fileReader =↵ new DefaultFileReader();
 
 @Test
 void cannotReadNonExistentFile() {
 assertThrows(IOException.class, () -> {
 fileReader.readContent(Paths.get("hello.text"));
 });
 }
 } Expec2ng thrown excep2ons

Slide 28

Slide 28 text

import spock.lang.FailsWith
 
 class ExpectedExceptionTest extends Specification {
 @Subject def fileReader = new DefaultFileReader()
 
 @FailsWith(IOException)
 def "throws exception if file contents cannot be read"() {
 expect:
 fileReader.readContent(Paths.get('hello.text'))
 }
 } Expec2ng thrown excep2ons

Slide 29

Slide 29 text

class ExpectedExceptionTest extends Specification {
 @Subject def fileReader = new DefaultFileReader()
 
 def "throws exception if file contents cannot be read↵ and assert message"() {
 when:
 fileReader.readContent(Paths.get('hello.text'))
 
 then:
 def t = thrown(IOException)
 t.message == 'File does not exist'
 } } Expec2ng thrown excep2ons

Slide 30

Slide 30 text

import org.junit.jupiter.api.RepeatedTest;
 
 public class RepetitionTest {
 private final ArithmeticOperation arithmeticOperation =↵ new ArithmeticOperation();
 
 @RepeatedTest(value = 5, name = "Iteration {currentRepetition}↵ of {totalRepetitions}")
 void canSubstract() {
 assertEquals(1, arithmeticOperation.substract(2, 1));
 }
 } Repea2ng test execu2on

Slide 31

Slide 31 text

import spock.lang.Unroll class RepetitionTest extends Specification {
 @Subject def arithmeticOperation =↵ new ArithmeticOperation()
 @Unroll
 def "Iteration #i of 5"() {
 expect:
 arithmeticOperation.substract(2, 1) == 1
 
 where:
 i << (1..5)
 }
 } Repea2ng test execu2on

Slide 32

Slide 32 text

import static java.time.Duration.ofSeconds;
 import static org.junit.jupiter.api.Assertions.assertTimeout;
 
 public class TimeoutTest {
 private final ArithmeticOperation arithmeticOperation =↵ new ArithmeticOperation();
 
 @Test
 void canAdd() {
 assertTimeout(ofSeconds(2), () -> {
 assertEquals(3, arithmeticOperation.add(1, 2));
 });
 }
 } Declaring test execu2on 2meouts

Slide 33

Slide 33 text

import spock.lang.Timeout
 
 class TimeoutTest extends Specification {
 @Subject def arithmeticOperation =↵ new ArithmeticOperation()
 
 @Timeout(2)
 def canAdd() {
 expect:
 arithmeticOperation.add(1, 2) == 3
 }
 } Declaring test execu2on 2meouts

Slide 34

Slide 34 text

Feeding data to a scenario

Slide 35

Slide 35 text

import org.junit.jupiter.params.*;
 import java.util.stream.Stream;
 
 public class DataDrivenTest {
 private final ArithmeticOperation arithmeticOperation =↵ new ArithmeticOperation();
 
 @ParameterizedTest(name = "can add {0} to {1} and receive {2}")
 @MethodSource("additionProvider")
 void canAddAndAssertExactResult(int a, int b, int result) {
 assertEquals(result, arithmeticOperation.add(a, b));
 }
 
 static Stream additionProvider() {
 return Stream.of(
 Arguments.of(1, 3, 4),
 Arguments.of(3, 4, 7)
 );
 }
 } Data-driven tests

Slide 36

Slide 36 text

Data-driven tests in IntelliJ

Slide 37

Slide 37 text

import spock.lang.Unroll
 
 class DataDrivenTest extends Specification {
 @Subject def arithmeticOperation = new ArithmeticOperation()
 
 @Unroll
 def "can add #a to #b and receive #result"() {
 expect:
 arithmeticOperation.add(a, b) == result
 
 where:
 a | b | result
 1 | 3 | 4
 3 | 4 | 7
 }
 } Data-driven tests

Slide 38

Slide 38 text

Data-driven tests in IntelliJ

Slide 39

Slide 39 text

Testing in isolation

Slide 40

Slide 40 text

import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 public class MockTest {
 ...
 
 @Test
 void canMockFileReadOperation() throws IOException {
 String text = "hello";
 FileReader fileReader = mock(FileReader.class);
 when(fileReader.readContent(testFile)).thenReturn(text);
 FileManager fileManager = new DefaultFileManager(fileReader);
 Files.write(testFile, text.getBytes());
 assertEquals(text, fileManager.readContent(testFile));
 }
 } Mocking dependencies

Slide 41

Slide 41 text

class MockTest extends Specification {
 def fileReader = Mock(FileReader)
 
 def "can mock file read operation"() {
 given:
 def text = "hello"
 Files.write(testFile, text.getBytes())
 
 when:
 def content = fileManager.readContent(testFile)
 
 then:
 1 * fileReader.readContent(testFile) >> text
 content == text
 }
 } Mocking dependencies

Slide 42

Slide 42 text

Categorized test execution

Slide 43

Slide 43 text

import org.junit.jupiter.api.Tag;
 
 public class TaggedTest {
 private final ArithmeticOperation arithmeticOperation =↵ new ArithmeticOperation();
 
 @Tag("slow")
 @Test
 void runsSlowly() {
 assertEquals(3, arithmeticOperation.add(1, 2));
 }
 
 @Tag("fast")
 @Test
 void runsFast() {
 assertEquals(1, arithmeticOperation.substract(2, 1));
 }
 } Labeling and filtering

Slide 44

Slide 44 text

import org.junit.platform.runner.JUnitPlatform;
 import org.junit.platform.suite.api.IncludeTags;
 import org.junit.platform.suite.api.SelectPackages;
 import org.junit.runner.RunWith;
 
 @RunWith(JUnitPlatform.class)
 @SelectPackages("com.bmuschko.test.comparison.junit5.tagged")
 @IncludeTags("fast")
 public class FilteredFastTest {
 } Labeling and filtering

Slide 45

Slide 45 text

class TaggedTest extends Specification {
 @Subject def arithmeticOperation = new ArithmeticOperation()
 
 @Slow
 def "runs slowly"() {
 expect:
 arithmeticOperation.add(1, 2) == 3
 }
 
 @Fast
 def "runs fast"() {
 expect:
 arithmeticOperation.substract(2, 1) == 1
 }
 } Labeling and filtering

Slide 46

Slide 46 text

@Retention(RetentionPolicy.RUNTIME)
 @Target([ElementType.TYPE, ElementType.METHOD])
 @interface Slow {
 }
 
 @Retention(RetentionPolicy.RUNTIME)
 @Target([ElementType.TYPE, ElementType.METHOD])
 @interface Fast {
 }
 Labeling and filtering

Slide 47

Slide 47 text

import com.bmuschko.test.comparison.spock.tagged.Fast
 
 runner {
 include Fast
 } Labeling and filtering SpockConfig.groovy

Slide 48

Slide 48 text

Extending the test framework

Slide 49

Slide 49 text

Third-party extensions JUnit Pioneer junit-pioneer.org Test engines, Docker, Mockito, WireMock and more… github.com/junit-team/junit5/wiki/Third-party-Extensions

Slide 50

Slide 50 text

Consuming an extension import org.junit.jupiter.api.extension.ExtendWith;
 
 @ExtendWith(BeforeAfterLoggingExtension.class)
 public class LoggingTest {
 private final ArithmeticOperation arithmeticOperation =↵ new ArithmeticOperation();
 
 @Test
 void canAdd() {
 assertEquals(3, arithmeticOperation.add(1, 2));
 }
 }

Slide 51

Slide 51 text

Wri2ng a simple extension import org.junit.jupiter.api.extension.*;
 
 public class BeforeAfterLoggingExtension implements↵ BeforeTestExecutionCallback, AfterTestExecutionCallback {
 @Override
 public void beforeTestExecution(ExtensionContext context) {
 Method testMethod = context.getRequiredTestMethod();
 System.out.println(String.format("Starting test method %s.%s”,↵ testMethod.getDeclaringClass(), testMethod.getName()));
 }
 
 @Override
 public void afterTestExecution(ExtensionContext context) {
 Method testMethod = context.getRequiredTestMethod();
 System.out.println(String.format("Finishing test method %s.%s”,↵ testMethod.getDeclaringClass(), testMethod.getName()));
 }
 }

Slide 52

Slide 52 text

Third-party extensions very liLle out there…

Slide 53

Slide 53 text

Consuming an extension @BeforeAfterLogging
 class LoggingTest extends Specification {
 @Subject def arithmeticOperation = new ArithmeticOperation()
 
 def "can add"() {
 expect:
 arithmeticOperation.add(1, 2) == 3
 }
 }

Slide 54

Slide 54 text

Wri2ng a simple extension import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
 import org.spockframework.runtime.model.SpecInfo
 
 class BeforeAfterLoggingExtension extends↵ AbstractAnnotationDrivenExtension {
 @Override
 void visitSpecAnnotation(BeforeAfterLogging annotation, SpecInfo spec) {
 spec.addListener(new BeforeAfterEventListener())
 }
 } import org.spockframework.runtime.extension.ExtensionAnnotation
 
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 @ExtensionAnnotation(BeforeAfterLoggingExtension.class)
 @interface BeforeAfterLogging {
 }

Slide 55

Slide 55 text

“Which framework is beLer?”

Slide 56

Slide 56 text

Accommodate your team’s skill set

Slide 57

Slide 57 text

Tests as readable user stories

Slide 58

Slide 58 text

Which features are crucial to you?

Slide 59

Slide 59 text

Resources junit.org/junit5 spockframework.org bmuschko.com/blog/junit5-vs-spock-showdown github.com/bmuschko/junit5-vs-spock-feature-comparison

Slide 60

Slide 60 text

Thanks! Please ask ques2ons.