Skip to content

Instantly share code, notes, and snippets.

@NateFuller
Last active June 30, 2022 14:29
Show Gist options
  • Save NateFuller/f4a427f97952895e48f79a793a774148 to your computer and use it in GitHub Desktop.
Save NateFuller/f4a427f97952895e48f79a793a774148 to your computer and use it in GitHub Desktop.
A wrapper for assertion throwing that we can use in Nimble/Quick so it doesn't crash test execution
protocol AssertionProvider {
func assertionFailure(_ message: String)
func fatalError(_ message: String)
}
final class AssertionHandler {
private(set) static var shared: AssertionHandler = AssertionHandler()
var provider: AssertionProvider = DefaultAssertionProvider()
private init() {}
func assertionFailure(_ message: String) {
provider.assertionFailure(message)
}
func fatalError(_ message: String) {
provider.fatalError(message)
}
func resetProvider() { provider = DefaultAssertionProvider() }
}
/**
The default assertion provider. Calls global-level methods for stopping program execution.
*/
final class DefaultAssertionProvider: AssertionProvider {
func assertionFailure(_ message: String) {
Swift.assertionFailure(message)
}
func fatalError(_ message: String) {
Swift.fatalError(message)
}
}
// Below - Usage example from within production code
class MyClass {
func methodThatFatalErrors() {
AssertionHandler.shared.fatalError("Fatal Error")
}
func methodThatAssertsFailure() {
AssertionHandler.shared.assertionFailure("Assertion Failure")
}
}
// Below - defined separately, probably in a module that only is used by your test target(s)
import Quick
import Nimble
@testable import ModuleThatDefines
// global, to be used by unit-tests
func expectAssertion<T>(_ closure: @escaping () -> T?) {
defer { ModuleThatDefines.AssertionHandler.shared.resetProvider() }
let assertionProvider = TestAssertionProvider()
ModuleThatDefines.AssertionHandler.shared.provider = assertionProvider
expect(closure()).to(assertionProvider.throwAssertion())
}
func expectAssertion<T>(_ closure: @escaping @autoclosure () -> T?) {
expectAssertion(closure)
}
/**
A test-only wrapper that allows us to circumvent Nimble not being able to capture `assertFailure` or `fatalError`
without crashing.
*/
class TestAssertionProvider: AssertionProvider {
private var errorMessage: String?
func assertionFailure(_ message: String) {
errorMessage = message
}
func fatalError(_ message: String) {
errorMessage = message
}
// MARK: - TestAssertionProvider specific
func throwAssertion<Out>() -> Predicate<Out> {
return Predicate { actualExpression in
_ = try! actualExpression.evaluate() // swiftlint:disable:this force_try
let message = ExpectationMessage.expectedTo("throw an assertion via TestAssertionProvider")
return PredicateResult(
bool: self.errorMessage != nil,
message: message)
}
}
}
// Below - Usage example from within unit tests
class MyClassSpec: QuickSpec {
override func spec() {
describe("MyClass") {
describe("methodThatFatalErrors") {
it("throws a fatal error") {
expectAssertion(MyClass().methodThatFatalErrors())
}
}
describe("methodThatAssertsFailure") {
it("asserts failure") {
expectAssertion(MyClass().methodThatAssertsFailure())
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment