/CheckXCAssertionFailure

Test your testing code! Allows you to write an `XCTest` that checks that a given expression causes some `XCAssert` function call to fail.

Primary LanguageSwiftApache License 2.0Apache-2.0

LoftTest_CheckXCAssertionFailure

XCTest-compatible components that can verify that a given test fails as expected.

Basic Usage

If you have written an XCAssert-style checking function like this one

import XCTest

/// `XCTAssert`'s that `s` is a palindrome, with the given failure message.
func myXCTAssertIsPalindrome(
  _ s: String, file: StaticString = #filePath, line: UInt = #line)
{
  // Not the most efficient way to check for palindrome-ness!
  XCTAssert(s.elementsEqual(s.reversed(), failureMessage, file: file, line: line)
}

and you want to test it, checking that it works when the test passes is easy:

  func testXCTAsssertIsPalindromePassesOnPalindrome() {
    myXCTAssertIsPalindrome("MadamInEdEnImadaM"))
  }

But to test that it works when the test fails, you need to somehow trigger the failure condition without causing any tests to actually fail.

To do that, derive you test case from CheckXCAssertionFailureTestCase:

import XCTest
import LoftTest_CheckXCAssertionFailure

final class MyXCTAssertionTests: CheckXCAssertionFailureTestCase {
    ...
}

and then, in the testing function, wrap the failing check in checkXCAssertionFailure:

  func testXCTAsssertIsPalindromeFailsOnNonPalindrome() {
    checkXCAssertionFailure( // <===== HERE
      myXCTAssertIsPalindrome("this is not a palindrome"))
  }

That test passes if, and only if, the XCTAssert in myXCTAssertIsPalindrome fails when passed the non-palindrome string.

Checking for a particular failure message

Testing myXCTAssertIsPalindrome is trivial, and one could make a case that it's not worth a dependency on this package. As reusable testing functions become more complex, though, it becomes harder to determine that they're doing the right things.

One reason is that they often contain many XCAssertions, and then just confirming that the test failed isn't enough: you need to confirm that it failed for the expected reasons, i.e. that the XCAssertion you expected is the one that fired. For a super-trivial example:

import XCTest

/// `XCTAssert`'s that `s` is a palindrome, with the given failure message.
func myXCTAssertIsNonEmptyPalindrome(
  _ s: String, file: StaticString = #filePath, line: UInt = #line
) {
  XCTAssertFalse(s.isEmpty, "empty string", file: file, line: line)
  XCTAssert(s.elementsEqual(s.reversed(), "not a palindrome", file: file, line: line)
}

To accomodate that need, checkXCAssertionFailure has an optional messageFragment parameter that makes the test pass only if the fragment is present in the failure message. In that case you might check for failure this way:

  func testXCTAsssertIsPalindromeFailureCases() {
    checkXCAssertionFailure(
      myXCTAssertIsNonEmptyPalindrome("asymmetric text"),
      messageFragment: "not a palindrome") // <==== HERE
      
    checkXCAssertionFailure(
      myXCTAssertIsNonEmptyPalindrome(""),
      messageFragment: "empty") // <==== HERE
  }

Failure to match

When evaluating the first argument to checkXCAssertionFailure doesn't produce a message matching the messageFragment, the resulting failure message contains a list of all the locations where an XCAssertion did fail, along with one example of any failure message that occurred on that line. This information can be useful for diagnosing messageFragment mismatches. Only one example is printed to avoid overwhelming the programmer in cases where a test fails in a loop.