Skip to content

Instantly share code, notes, and snippets.

@nschum
Created July 10, 2015 19:06
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nschum/208b3dde43785afd439a to your computer and use it in GitHub Desktop.
Save nschum/208b3dde43785afd439a to your computer and use it in GitHub Desktop.
Testing precondition (or assert) in Swift
/// Our custom drop-in replacement `precondition`.
///
/// This will call Swift's `precondition` by default (and terminate the program).
/// But it can be changed at runtime to be tested instead of terminating.
func precondition(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = "", file: StaticString = __FILE__, line: UWord = __LINE__) {
preconditionClosure(condition(), message(), file, line)
}
/// The actual function called by our custom `precondition`.
var preconditionClosure: (Bool, String, StaticString, UWord) -> () = defaultPreconditionClosure
let defaultPreconditionClosure = {Swift.precondition($0, $1, file: $2, line: $3)}
import XCTest
extension XCTestCase {
func expectingPreconditionFailure(expectedMessage: String, @noescape block: () -> ()) {
let expectation = expectationWithDescription("failing precondition")
// Overwrite `precondition` with something that doesn't terminate but verifies it happened.
preconditionClosure = {
(condition, message, file, line) in
if !condition {
expectation.fulfill()
XCTAssertEqual(message, expectedMessage, "precondition message didn't match", file: file.stringValue, line: line)
}
}
// Call code.
block();
// Verify precondition "failed".
waitForExpectationsWithTimeout(0.0, handler: nil)
// Reset precondition.
preconditionClosure = defaultPreconditionClosure
}
}
// example /////////////////////////////////////////////////////////////////////////////////////////
func doSomething() {
precondition(false, "just not true")
}
class TestCase: XCTestCase {
func testExpectPreconditionFailure() {
expectingPreconditionFailure("just not true") {
doSomething();
}
}
}
@checkerrorcode
Copy link

Super great !
++

@niklasnickel
Copy link

Thank you very much @nschum for providing your solution. In order to make it work with Swift 5.6 you need to change a few things. I have provided a working version below. I have also done some minor refactoring as of personal preference, which are not required for it to work.

Precondition.swift

func precondition(_ condition: @autoclosure () -> Bool,  _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
	preconditionClosure(condition(), message(), file, line)
}

/// The actual function called by our custom `precondition`.
var preconditionClosure = defaultPreconditionClosure
let defaultPreconditionClosure = {Swift.precondition($0, $1, file: $2, line: $3)}

XCTest+Precondition.swift

import XCTest

extension XCTestCase {
	func assertPreconditionFailure(expectedMessage: String, block: () -> ()) {
		let expectation = expectation(description: "failing precondition")
		
		// Overwrite `precondition` with something that doesn't terminate but verifies it happened.
		preconditionClosure = {
			(condition, message, file, line) in
			if !condition {
				expectation.fulfill()
				XCTAssertEqual(message, expectedMessage, "precondition message didn't match", file: file, line: line)
			}
		}
		block() // Call code.
		waitForExpectations(timeout: 0.0, handler: nil) // Verify precondition "failed".
		preconditionClosure = defaultPreconditionClosure // Reset precondition.
	}
}

This version can be used like the example @nschum provided.

func doSomething() {
	precondition(false, "just not true")
}

class TestCase: XCTestCase {
	func testExpectPreconditionFailure() {
		assertPreconditionFailure("just not true") {
			doSomething()
		}
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment