A straightforward yet powerful Swift macro designed to simplify the unit test creation process. This tool allows you to easily parameterize your XCTest methods by automatically generating test methods based on specified arguments. Write your test methods once and effortlessly generate variations with different parameters. This approach not only simplifies test maintenance but also makes identifying failing tests more effective. Inspired by JUnit parametrized tests.
Xcode 15 or above Swift 5.9 or later
Xcode project
If you are using Xcode open File -> Add Package Dependencies...
and enter url https://github.com/pgssoft/XCTestParametrizedMacro
Swift package manager
In Package.swift
add:
dependencies: [
.package(url: "https://github.com/pgssoft/XCTestParametrizedMacro", branch: "main")
]
and then add the product to your test target.
.product(name: "XCTestParametrizedMacro", package: "XCTestParametrizedMacro"),
and import it in your unit test file
import XCTestParametrizedMacro
Let's consider a simple example. You want to implement unit tests for the method validating the age of the user.
struct AgeValidator {
static func isAdult(age: Int) -> Bool {
age >= 18
}
}
If you want to test it for a few values you can write two generic methods and use macro to create a separate test method for each value.
@Parametrize(input: [18, 25, 99])
func testAgeValidatorValidAge(input age: Int) throws {
XCTAssertTrue(AgeValidator.isAdult(age: age))
}
@Parametrize(input: [2, 17])
func testAgeValidatorInvalidAge(input age: Int) throws {
XCTAssertFalse(AgeValidator.isAdult(age: age))
}
Macro will generate a test method for every case.
func testAgeValidatorValidAge_Age_18() throws {
let age: Int = 18
XCTAssertTrue(AgeValidator.isAdult(age: age))
}
func testAgeValidatorValidAge_Age_25() throws {
let age: Int = 25
XCTAssertTrue(AgeValidator.isAdult(age: age))
}
func testAgeValidatorValidAge_Age_99() throws {
let age: Int = 99
XCTAssertTrue(AgeValidator.isAdult(age: age))
}
func testAgeValidatorInvalidAge_Age_2() throws {
let age: Int = 2
XCTAssertFalse(AgeValidator.isAdult(age: age))
}
func testAgeValidatorInvalidAge_Age_17() throws {
let age: Int = 17
XCTAssertFalse(AgeValidator.isAdult(age: age))
}
But as an input parameter, you can use custom types as well.
@Parametrize(input: [APIEndpoint.profile, APIEndpoint.transactions, APIEndpoint.order("2345")])
func testEndpointURL(input endpoint: APIEndpoint) throws {
XCTAssertNotNil(endpoint.buildURL)
}
The macro will generate the following test methods.
func testEndpointURL_Endpoint_APIEndpoint_profile() throws {
let endpoint: APIEndpoint = APIEndpoint.profile
XCTAssertNotNil(endpoint.buildURL)
}
func testEndpointURL_Endpoint_APIEndpoint_transactions() throws {
let endpoint: APIEndpoint = APIEndpoint.transactions
XCTAssertNotNil(endpoint.buildURL)
}
func testEndpointURL_Endpoint_APIEndpoint_order_2345_() throws {
let endpoint: APIEndpoint = APIEndpoint.order("2345")
XCTAssertNotNil(endpoint.buildURL)
}
You can also specify array of expected values and use it in your test.
@Parametrize(
input: [APIEndpoint.profile,
APIEndpoint.transactions,
APIEndpoint.order("2345")],
output: ["https://example.com/api/me",
"https://example.com/api/transactions",
"https://example.com/api/order/2345"])
func testEndpointURL(input endpoint: APIEndpoint, output expectedUrl: String) throws {
XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl)
}
Because for example above names of test methods looks weird, You can use labels
parameter to use custom naming.
@Parametrize(
input: [APIEndpoint.order("2345")],
output: ["https://example.com/api/order/2345"]),
labels: ["order"])
func testEndpointURL(input endpoint: APIEndpoint, output expectedUrl: String) throws {
XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl)
}
Macro will generate following test method.
func testEndpointURL_order() throws {
let endpoint: APIEndpoint = APIEndpoint.order("2345")
let expectedUrl: String =
"https://example.com/api/order/2345"
XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl)
}
- Primitive types as input values (like Int, Double, String and Bool)
- Custom objects as input values (like structs, classes and enums)
- Diagnostics for error handling
- Expected output array of objects/values
- Labels for parameter values like:
@Parametrize(input: [3.14, 2.71], labels: ["pi", "e"])
- Array of tuple objects as macro argument
XCTestParametrizedMacro is released under an MIT license. See LICENSE