Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing assertion in Swift

I'm writing unit tests for a method that has an assertion. The Swift Language guide recommends using assertions for "invalid conditions":

Assertions cause your app to terminate and are not a substitute for designing your code in such a way that invalid conditions are unlikely to arise. Nonetheless, in situations where invalid conditions are possible, an assertion is an effective way to ensure that such conditions are highlighted and noticed during development, before your app is published.

I want to test the failure case.

However, there is not XCTAssertThrows in Swift (as of Beta 6). How can I write an unit test that tests that an assertion fails?

Edit

As per @RobNapier's suggestion, I tried wrapping XCTAssertThrows in an Objective-C method and calling this method from Swift. This doesn't work as the macro does not catch the fatal error caused by assert, and thus the test crashes.

like image 893
hpique Avatar asked Aug 27 '14 14:08

hpique


People also ask

What is assertion mean in testing?

At the basic level, an assertion is just a Boolean expression. It contains a true and false binary. The expression is placed into the testing program and pertains to a certain section of the software being tested. The assertion itself encompasses an expression that describes the logic of the code under test.

What is assertion in Swift?

Assertions allow us to have Swift silently check the state of our program at runtime, but if you want to get them right you need to understand some intricacies. In this article I'll walk you through the five ways we can make assertions in Swift, and provide clear advice on which to use and when.

What are assertions in test cases?

An assertion is a boolean expression at a specific point in a program which will be true unless there is a bug in the program. A test assertion is defined as an expression, which encapsulates some testable logic specified about a target under test.

What is Xctassert?

A literal NSString , optionally with string format specifiers.


2 Answers

assert and its sibling precondition don't throw exceptions cannot be "caught" (even with Swift 2's error handling).

A trick you can use is to write your own drop-in replacement that does the same thing but can be replaced for tests. (If you're worried about performance, just #ifdef it away for release builds.)

custom precondition

/// 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)} 

test helper

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();         }     } } 

(gist)

Similar code will work for assert, of course. However, since you're testing the behavior, you obviously want it to be part of your interface contract. You don't want optimized code to violate it, and assert will be optimized away. So better use precondition here.

like image 61
nschum Avatar answered Sep 20 '22 05:09

nschum


Agree with nschum's comment that it doesn't seem right to unit test assert because by default it wont be in the prod code. But if you really wanted to do it, here is the assert version for reference:

override assert

func assert(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = "", file: StaticString = __FILE__, line: UInt = __LINE__) {     assertClosure(condition(), message(), file, line) } var assertClosure: (Bool, String, StaticString, UInt) -> () = defaultAssertClosure let defaultAssertClosure = {Swift.assert($0, $1, file: $2, line: $3)} 

helper extension

extension XCTestCase {      func expectAssertFail(expectedMessage: String, testcase: () -> Void) {         // arrange         var wasCalled = false         var assertionCondition: Bool? = nil         var assertionMessage: String? = nil         assertClosure = { condition, message, _, _ in             assertionCondition = condition             assertionMessage = message             wasCalled = true         }          // act         testcase()          // assert         XCTAssertTrue(wasCalled, "assert() was never called")         XCTAssertFalse(assertionCondition!, "Expected false to be passed to the assert")         XCTAssertEqual(assertionMessage, expectedMessage)          // clean up         assertClosure = defaultAssertClosure     } } 
like image 35
Ken Ko Avatar answered Sep 21 '22 05:09

Ken Ko