XCUITest — Smarter XCTest Skips
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 = .passAccessing the properties:
s1.message //Returns ""
s2.message //Returns "Something went wrong"
s3.isError //Returns "false" - avoids directly checking enum casesOptionally, 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
addStepcan 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..







