17

UPDATE:

It looks like this functionality should be supported in the upcoming Swift 2.0/XCode 7 versions, which apparently will include try/catch goodness, so this question will likely be moot. I'll try to update this post accordingly when they are out of beta.

ORIGINAL QUESTION:

In Swift, though I assume the question/answer would be applicable to Objective-C, I want to write a function foo of the format:

public class SomeClass{
  public func foo(someString:String){
    //validate someString
    assert(!someString.isEmpty, "The someString parameter cannot be empty.")
  }
}

I use an assert call because I believe this is what is recommended by Apple as opposed to throwing exceptions, as is common in other languages.

However, in my unit test, I want to be able to ensure that the function indeed fails when the someString parameter is an empty String:

class SomeClass_Tests:XCTestCase{
  func test_foo_someStringParamaterIsEmpty_error(){
    //ACTION
    let someClassInstance = SomeClass()
    someClassInstance.foo("")

    //VALIDATE
    //**What goes here?
  }
}

I can find no documentation or posts regarding this situation, even though I believe this is a highly important unit test to ensure proper behavior and usage of classes and libraries.

In other languages that include exceptions/exception handling, the assert would be replaced with something like throw SomeError() and then, in the unit test you could simply wrap the action in a try/catch block and assert that the exception was indeed set, like this:

class SomeClass_Tests:XCTestCase{
  func test_foo_someStringParamaterIsEmpty_error(){
    //ACTION
    let someClassInstance = SomeClass()

    var expectedException:SomeException? = nil
    try{
      someClassInstance.foo("")
    }catch(someException:SomeException){
      expectedException = someException
    }

    //VALIDATE
    XCTAssertIsNotNil(expectedException)
  }
}

But there are no such constructs or equivalent work-arounds in Swift that I've seen in the documentation. Are there any known solutions or workarounds for performing tests like this?

Zachary Smith
  • 169
  • 1
  • 1
  • 6
  • 1
    You’re absolutely right that XCTest ought to have a did-assert or did-not-assert assertion (though it’s slightly complicated by build configs enabling/disabling assertions - more useful to test preconditions and fatal errors probably). Brian Gesiak wrote [this post](http://modocache.io/xctest-the-good-parts) about some of the limitations and filed [this radar](http://openradar.appspot.com/20066350) about assertions specifically. – Airspeed Velocity May 21 '15 at 19:19
  • Thanks @AirspeedVelocity, those links were very useful. I think the lack of true "exceptions" was forged through a bit of hubris (reminiscent of when junior developers equate "exception throwing" with "bug throwing"), and for me, is quickly becoming a sore spot on what I think is otherwise a reasonably enjoyable language/framework. – Zachary Smith May 21 '15 at 20:25
  • I’m on the fence about exceptions. On the one hand, yes it would be nice to be able to catch accidental overflows. On the other hand, exceptions commonly used for non-truly-exceptional reasons are a complete curse in other languages… So in conclusion, I will never be happy... – Airspeed Velocity May 21 '15 at 20:28
  • Oh yeah, exceptions are definitely a classic case of the whole great power/great responsibility schtick. – Zachary Smith May 21 '15 at 20:42
  • Possible duplicate of [Testing assertion in Swift](http://stackoverflow.com/questions/25529625/testing-assertion-in-swift) – nschum Dec 20 '15 at 21:44

4 Answers4

9

Matt Gallagher's CwlPreconditionTesting project on github adds a catchBadInstruction function which gives you the ability to test for assertion/precondition failures in unit test code.

The CwlCatchBadInstructionTests file shows a simple illustration of its use. (Note that it only works in the simulator for iOS.)

user2067021
  • 4,399
  • 37
  • 44
  • 2
    `CwlPreconditionTesting` library is integrated in Nimble now : https://github.com/Quick/Nimble#swift-assertions – Axel Guilmin Oct 22 '19 at 09:38
  • 1
    second link is dead. link https://github.com/mattgallagher/CwlPreconditionTesting/blob/master/Tests/CwlPreconditionTestingTests/CwlCatchBadInstructionTests.swift – fingia Jun 28 '22 at 17:17
1

However, in my unit test, I want to be able to ensure that the function indeed fails when the someString parameter is an empty String

I understand just what you're trying to do, because I've done it myself in Ruby programs. But Swift (as I have had frequent occasion to observe) is not Ruby! The problem here is that you can guarantee that the function does not fail when the someString parameter is an empty string — in your actual app. This is because asserts don't operate in a release build.

The upshot is that you can use assert as a form of debugging during development, but if such a case can occur in real life, you should be handling it in good order, not crashing.

And thus testing for whether an assert "happened" is not really a valid unit testing technique, which is why you're having trouble using it in that way.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • "but if such a case can occur in real life, you should be handling it in good order, not crashing." - to me that misses the point of exceptions. Exceptions, to me, are how developers indicate to other developers (or themselves) that a method or class is being misused in an _informative_ manner, rather than just crashing internally and inconsistently. I think it actually runs philosophically counter to the lines of thinking behind the `!` nilable operator. "Here is this super-easy debugging/production-crash-causing operator to make things safer...and...and that's all you get! Go away!" :P – Zachary Smith May 21 '15 at 20:39
  • 1
    "to me that misses the point of exceptions" I totally agree; hence my remark about doing this in Ruby. But I think _you_ totally miss the point of Swift not _having_ exceptions! (At least, not as a form of flow control. Yes, you can still crash at runtime, how can it be denied?!!!) I am not saying I _agree_ with Swift's philosophy, but I am saying this _is_ the philosophy and that is why you cannot do what you want to do here. – matt May 21 '15 at 21:34
  • exceptions are a common objective-c paradigm for conveying fatal errors and it is to be expected that swift programs interoperating with objective-c libraries need a way to work with that paradigm. – emish Dec 05 '17 at 04:19
  • 1
    @emish They do now! But they didn’t back when I wrote that answer. Swift has changed greatly since those days. You’re looking at history. – matt Dec 05 '17 at 15:11
1

why to try and catch the error and than checking what happened instead of testing that assertion is thrown ?

XCTAssertThrows()

and than to see the values ?

Dan Kuida
  • 1,017
  • 10
  • 18
  • 3
    XCTAssertThrows is not directly available in Swift. – Zachary Smith Jun 10 '15 at 17:35
  • 1
    As of Swift 2 there is XCTAssertThrowsError, see https://stackoverflow.com/questions/36190161/xctassertthrowserror-strange-behavior-with-custom-errorhandler – Aron Mar 31 '18 at 11:52
  • 1
    XCTAssertThrows is a function that asserts that something throws an exception. That is different from "catching" an `assert` statement. – sofacoder Nov 04 '19 at 11:56
0

You can checkout the XCTAssertThrows and XCTAssertThrowsSpecific macros available in XCTest framework.

Maybe you will need to replace your assert with [NSException raise] to make it work.

Eugene Dudnyk
  • 5,553
  • 1
  • 23
  • 48
  • Those macros are not available in the Swift language, so my original assumption that answers would be applicable to Objective-C was incorrect. This [post](http://stackoverflow.com/questions/24052739/swift-unit-testing-with-xctassertthrows-analogue) is very similar and offers some suggestions and information. – Zachary Smith May 21 '15 at 22:47
  • You can consider to switch your XCTestCase module to ObjC language if you really need to test the assertion. – Eugene Dudnyk May 21 '15 at 23:34
  • Or (maybe it is not possible, I'm writing in swift 2 weeks only) you can try to add an ObjC category to XCTestCase swift module with a specific method which tests assertion only. – Eugene Dudnyk May 21 '15 at 23:40