Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift - Display assertion failures inline in Xcode like XCTest failures?

I'd like to write some custom test assertion types and have them be displayed in Xcode, similarly to how the XCTAssert() failures are displayed:

Is there a way for me to hook into Xcode and make this happen?

I want my own assertion function to show its error inline the same way here:

enter image description here

The best resource I've found so far is Apple's XCTest source code, but I haven't been able to understand if that even includes the logic that is responsible for displaying the error UI.

like image 865
bkbeachlabs Avatar asked Nov 08 '18 03:11

bkbeachlabs


2 Answers

The easiest way is to call XCTFail() from your custom assertion, but pass along the file name and line number of the call site. For example:

func verify(myThing: MyThing, file: StaticString = #filePath, line: UInt = #line) {
   // Do your verification. Then when you want to fail the test,
   XCTFail("Some message about \(myThing)", file: file, line: line)
}

At the call site, you'd let the default arguments provide file and line. So it would just look like:

verify(myThing: thing)

In Swift, XCTest assertions are global functions. This means your helper can also be a global function, and shared across test suites without having to subclass XCTestCase.

like image 188
Jon Reid Avatar answered Oct 20 '22 05:10

Jon Reid


It is possible to do exactly what you want, I just managed to do it, like this:

Use recordFailure

You can achieve what you want just by calling recordFailure, in any test (inheriting from standard XCTestCase).

Shorter syntax

Either extend XCTestCase

If you want to simplify the call to this function you either write an extension function wrapping it.

or subclass

You can also subclass XCTestCase (which can be a good idea if you want to share some setup, being called in setup, then you only need to do it in this new superclass to your test classes).

class TestCase: XCTestCase {

  func forwardFailure(
        withDescription description: String = "Something went wrong",
        inFile filePath: String = #file,
        atLine line: Int = #line,
        expected: Bool = false
    ) {
        self.recordFailure(
            withDescription: description,
            inFile: filePath,
            atLine: line,
            expected: expected
        )
    }

}

I'm not sure how to use expected: Bool, it is a required argument for the recordFailure method (source), but looks like Apple mostly sets it to false.

Custom assert methods

Now you can declare your custom assert methods, like this (or as an extension on just XCTestCase, depending on your choice):


extension TestCase {

    /// Since `Foobar.Type` does not conform to `Equatable`, even if `Foobar` conforms to `Equatable`, we are unable to write `XCTAssertEquals(type(of: foo), Foobar.self)`, this assert method fixes that.
    func XCTAssertType<Actual, Expected>(
        of actual: Actual,
        is expectedType: Expected.Type,

        _ filePath: String = #file,
        _ line: Int = #line
    ) {

        if let _ = actual as? Expected { return /* success */  }
        let actualType = Mirror(reflecting: actual).subjectType

        forwardFailure(
            withDescription: "Expected '\(actual)' to be of type '\(expectedType)', but was: '\(actualType)'",

            inFile: filePath,
            atLine: line
        )
    }

}

And now it reports the error, not inside the custom assert, but at call site πŸŽ‰.

Using standard assert methods + forward location in file

You can also just forward the line of, and pass to standards asserts, if you look at e.g. XCTAssertTrue, it accepts a line argument, and a file argument.

like image 40
Sajjon Avatar answered Oct 20 '22 05:10

Sajjon