Skip to content

Include the location of a parameterized test argument which recorded an issue (if possible) in console output for better traceability #1037

Closed as duplicate of#519
@ojun9

Description

@ojun9

Motivation

When using @Test(arguments:) to test multiple input patterns in a single test function, it can be difficult to identify which argument caused a failure, especially when several similar test cases are used.

For example, consider the following test setup:

Supporting code:

struct User: Equatable {
    let name: String
    let age: Int
    let email: String
    let username: String
    let password: String
    let isActive: Bool
    let signupDate: Date
    let lastLoginDate: Date
    let phoneNumber: String
    let address: String
    let bio: String
}

extension User {
    static func mock(
        name: String = "John Doe",
        age: Int = 30,
        email: String = "john@example.com",
        username: String = "johndoe",
        password: String = "password123",
        isActive: Bool = true,
        signupDate: Date = Date(),
        lastLoginDate: Date = Date(),
        phoneNumber: String = "000-0000-0000",
        address: String = "123 Apple Street",
        bio: String = "Just a test user."
    ) -> Self {
        .init(
            name: name,
            age: age,
            email: email,
            username: username,
            password: password,
            isActive: isActive,
            signupDate: signupDate,
            lastLoginDate: lastLoginDate,
            phoneNumber: phoneNumber,
            address: address,
            bio: bio
        )
    }
}

Test code:

struct UserTests {
    @Test(arguments: [
        User.mock(),
        .mock(name: "hoge", isActive: false),
        .mock(lastLoginDate: .distantPast, bio: "Hello World"),

        // Multiple mock cases follow

        .mock(email: "fuga@example.com"),
        .mock(phoneNumber: "999-9999-9999")
    ])
    func someTest(_ user: User) async throws {
        let expected: User = .mock() // In practice, this would call some logic
        #expect(user == expected)
    }
}

When a test fails, the output looks like this:

✘ Test someTest(_:) recorded an issue with 1 argument user → User(name: "John Doe", age: 30, email: "john@example.com", username: "johndoe", password: "password123", isActive: true, signupDate: 2025-03-25 09:59:04 +0000, lastLoginDate: 0001-01-01 00:00:00 +0000, phoneNumber: "000-0000-0000", address: "123 Apple Street", bio: "Hello World") at TestTests.swift:72:9: Expectation failed: (user → User(name: "John Doe", age: 30, email: "john@example.com", username: "johndoe", password: "password123", isActive: true, signupDate: 2025-03-25 09:59:04 +0000, lastLoginDate: 0001-01-01 00:00:00 +0000, phoneNumber: "000-0000-0000", address: "123 Apple Street", bio: "Hello World")) == (expected → User(name: "John Doe", age: 30, email: "john@example.com", username: "johndoe", password: "password123", isActive: true, signupDate: 2025-03-25 09:59:04 +0000, lastLoginDate: 2025-03-25 09:59:04 +0000, phoneNumber: "000-0000-0000", address: "123 Apple Street", bio: "Just a test user."))

However, this output alone doesn’t clearly indicate which argument (i.e., which entry in the arguments array) caused the failure. This makes it difficult to trace which case needs to be fixed, especially in large or complex test sets.

Proposed solution

A helpful enhancement would be to include the argument’s index in the failure output. For example, if the second item in the arguments array fails, the output might look like this:

✘ Test someTest(_:) [arguments index: 2] recorded an issue with 1 argument user → User(name: "John Doe", age: 30, email: "john@example.com", username: "johndoe", password: "password123", isActive: true, signupDate: 2025-03-25 09:59:04 +0000, lastLoginDate: 0001-01-01 00:00:00 +0000, phoneNumber: "000-0000-0000", address: "123 Apple Street", bio: "Hello World") at TestTests.swift:72:9: Expectation failed: (user → User(name: "John Doe", age: 30, email: "john@example.com", username: "johndoe", password: "password123", isActive: true, signupDate: 2025-03-25 09:59:04 +0000, lastLoginDate: 0001-01-01 00:00:00 +0000, phoneNumber: "000-0000-0000", address: "123 Apple Street", bio: "Hello World")) == (expected → User(name: "John Doe", age: 30, email: "john@example.com", username: "johndoe", password: "password123", isActive: true, signupDate: 2025-03-25 09:59:04 +0000, lastLoginDate: 2025-03-25 09:59:04 +0000, phoneNumber: "000-0000-0000", address: "123 Apple Street", bio: "Just a test user."))

This small addition would make it significantly easier to identify the failing case and improve the debugging experience, especially when dealing with multiple test arguments.

Currently, developers can work around this by wrapping the argument in a custom struct with a label, like so:

struct UserTestCase {
    let label: String
    let user: User
}

@Test(arguments: [
    UserTestCase(label: "default", user: .mock()),
    UserTestCase(label: "inactive", user: .mock(name: "hoge", isActive: false)),
])
func someTest(_ testCase: UserTestCase) async throws {
    #expect(testCase.user == .mock(), "Failure in case: \(testCase.label)")
}

While this pattern works, it adds boilerplate and isn’t ideal for simpler test cases.
Including the argument index in the default output would be a lightweight and widely useful improvement that benefits many test scenarios.

Alternatives considered

No response

Additional information

No response

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestparameterized-testingRelated to parameterized testing functionality

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions