Sitemap

XCUITest — Smarter XCTest Skips

2 min readMay 27, 2025

--

In certain scenarios, we encounter cases where a test shouldn’t fail, but also shouldn’t run — such as when a required precondition isn’t met, an external dependency is unavailable, or the test simply doesn’t apply to a specific environment or configuration. These are ideal situations where skipping a test is more appropriate than failing it.

To handle this, Apple provides XCTSkip, along with convenient helpers like XCTSkipIf and XCTSkipUnless.

Here’s a quick example:

func test_something() throws {
guard checkNetworkAvailability() else {
throw XCTSkip("Network is unavailable - Skipping test case")
}

// or XCTSkipIf(!checkNetworkAvailability(), "Network is unavailable – Skipping test case")

// Test logic...
}

func checkNetworkAvailability() -> Bool {
let isAvailable = false // Replace with real logic
return isAvailable
}

Simple? Yes. Scalable? Not quite!

When you have multiple checks or preconditions, cluttering tests with conditionals becomes messy. Plus, it becomes harder to log reasons or reuse logic.

If a method returns a value indicating whether it successfully completed its work, it becomes much easier to decide whether a test should proceed or be skipped.

To represent this completion state more clearly, we can define an enum that captures the outcome of a function’s execution.

enum Status: Equatable {
case pass
case fail(error: String)

var message: String {
switch self {
case .pass: return ""
case .fail(let error): return error
}
}

var isError: Bool {
if case .fail = self { return true }
return false
}

static var fail: Status {
.fail(error: "")
}
}

We’ve defined two possible states: pass and fail. The fail case includes an associated value, allowing you to optionally provide a reason for the failure.

Get XCUITest’s stories in your inbox

Join Medium for free to get updates from this writer.

This enum can be used as follows:

let s1: Status = .fail //Without reason
let s2: Status = .fail(error: "Something went wrong") //With reason
let s3: Status = .pass

Accessing the properties:

s1.message //Returns ""
s2.message //Returns "Something went wrong"
s3.isError //Returns "false" - avoids directly checking enum cases

Optionally, you could make the message property return a String? instead of an empty string to better represent the presence or absence of an error message.

Usage:

Start by defining a function that returns a Status:

func checkNetworkAvailability() -> Status {
let isAvailable = false // Replace with real logic
return isAvailable ? .pass : .fail(error: "No network connection")
}

Then use this helper in your test case:

func test_something() throws {
let status = checkNetworkAvailability()
try skipIfFailed(status)

// Continue with test
}

And here’s the skipIfFailed utility:

func skipIfFailed(_ status: Status) throws {
guard status == .pass else {
addStep("Skipping test case. Reason: \(status.message)") {}
throw XCTSkip(status.message)
}
}

To learn more about how addStep can enhance your test reports, check out this story.

By using a lightweight enum like Status, you provide your test logic with a simple and expressive way to answer the question: "Can I proceed?" It helps avoid cluttered if conditions and gives you a clean, reusable mechanism to decide when a test should be skipped rather than failed.

Happy skipping..

--

--