Example Parameterized Tests: Java, Kotlin, and Swift

Last time (“Extracting” in References), we looked at why you’d use parameterized tests, to reduce duplication, improve clarity, and improve test quality. This time we’ll look at an example parameterized test in three languages: Java, Kotlin, and Swift.

Similar code with a few spots of variation to be parameterized

Java Example

JUnit, the standard test package, supports parameterized tests directly. Your parameterized method can get values directly, from an enum, from CSV strings or files, or provided by another method.

Here, we use the latter technique: shortParsesCorrectly() is the test method, and shortsAsStrings() provides the data.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class ParamTest {

  @ParameterizedTest
  @MethodSource("shortsAsStrings")
  void shortParsesCorrectly(
        String input, short expected, String why) {
      var actual = Short.valueOf(input);
      assertEquals(expected, actual, why);
  }

  private static Stream<Arguments> shortsAsStrings() {
    return Stream.of(
      Arguments.of("000", Short.valueOf((short) 0), "zero"),
      Arguments.of("-32768", Short.MIN_VALUE, "minimum"),
      Arguments.of("32767", Short.MAX_VALUE, "maximum"),
      Arguments.of("010", Short.valueOf((short) 10), "decimal even with leading 0"),
      Arguments.of("+32767", Short.MAX_VALUE, "max with leading +")
    );
  }
}

Kotlin Example

Kotlin can also use JUnit’s parameterized support. It uses companion object as an analog to Java’s static, with @JvmStatic for interoperability. Notice that I’ve used the “why” in the @ParameterizedTest‘s name attribute.

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource

import java.util.stream.Stream

class PTest {
  @ParameterizedTest(name = "Short: {2}")
  @MethodSource("shortsAsStrings")
  fun shortParsesCorrectly(
      input: String, expected: Short, why: String) {
    val actual = input.toShort()
    assertEquals(expected, actual)
  }

  companion object {
    @JvmStatic
    fun shortsAsStrings(): Stream<Arguments> {
      return Stream.of(
        Arguments.of("000",    0.toShort(),     "zero"),
        Arguments.of("-32768", Short.MIN_VALUE, "minimum"),
        Arguments.of("32767",  Short.MAX_VALUE, "maximum"),
        Arguments.of("010",    10.toShort(),    "decimal even with leading 0"),
        Arguments.of("+32767", Short.MAX_VALUE, "max with leading +")
      )
    }
  }
}

Swift Example

Unlike Java, Swift doesn’t have parameterized tests in its standard test library XCTest. We can make our own parameterized tests: hold the data values in an array and loop over them. (This approach is what I used in “Parameterized Unit Testing” in References, and may be the start of a framework.)

import XCTest

class PDemoTests: XCTestCase {

  struct Example<Input, Output> {
    var input: Input
    var output : Output
    var message: String
    var line: Int

    init(input: Input, output: Output,
         _ message: String = "", _ line: Int = #line) {
      self.input = input
      self.output = output
      self.message = message
      self.line = line
    }

    func msg() -> String {
      return "Line \(line): \(message)"
    }
  }

  func testStringToInt16() {
    [
      Example(input: "000", output: Int16(0), "zero"),
      Example(input: "-32768", output: Int16(INT16_MIN), "min value"),
      Example(input: "32767", output: Int16(INT16_MAX), "max value"),
      Example(input: "010", output: Int16(10), "decimal value"),
      Example(input: "+32767", output: Int16(INT16_MAX), "leading +")
    ].forEach { example in
      let actual = Int16(example.input)
      XCTAssertEqual(example.output, actual, example.msg())
    }
  }
}

I used a helper class, but a tuple (anonymous record) would have worked fine too.

Conclusion

Whether your language has a parameterized test framework (Java, Kotlin), or you have to build your own parameterized tests (Swift), you can get the benefits of this style of test.

References

Extracting Parameterized Unit Tests“, by Bill Wake. Retrieved 2021-09-12.

Guide to JUnit 5 Parameterized Tests“, by Ali Dehghani. Retrieved 2021-09-13.

Parameterized Unit Testing“, by Bill Wake. Retrieved 2021-09-12.

Start Here: TDD