【问题标题】:XCTAssertThrowsError failing due to thrown error. XCTAssertThrowsError failed: throwing Invalid type in JSON write (NSObject)XCTAssertThrowsError 由于抛出错误而失败。 XCTAssertThrowsError 失败:在 JSON 写入中抛出无效类型(NSObject)
【发布时间】:2020-03-17 17:32:35
【问题描述】:

我有一个从字典创建 JSON 数据的函数,并指定它 throws 将错误向上传播到堆栈:

func createBodyDataFrom(dictionary: [String: Any]) throws -> Data {
        let bodyData = try JSONSerialization.data(withJSONObject: dictionary, options: [])

        return bodyData
    }

但是,根据 XCode,当使用 XCTAssertThrowsError 进行测试时,我会遇到测试失败,因为该函数引发了异常

func testCreateJSONFrominValidDictionaryThrows() {
        let validDictionary: [String: Any] = [
            "object": NSObject()
        ]
        XCTAssertThrowsError(try testClient.createBodyDataFrom(dictionary: validDictionary))
    }

来自 IDE 的失败消息给出:

XCTAssertThrowsError 失败:在 JSON 写入 (NSObject) 中抛出无效类型

这似乎是矛盾的,但让失败案例未经测试感觉不完整。任何想法这里出了什么问题?

【问题讨论】:

    标签: swift xctest


    【解决方案1】:

    让我们从在 Objective-C 中运行一些等效的代码开始,以便我们了解 Cocoa 在这里做了什么:

    NSDictionary* d = @{@"Howdy": [NSObject new]};
    NSError* err;
    [NSJSONSerialization dataWithJSONObject:d options:0 error:&err];
    NSLog(@"%@", err);
    

    如果这是导致 NSError 的事情,我们将能够读取 err 的值并将其打印到控制台。但事实并非如此,我们也不能。我们从未接触过 NSLog 语句。相反,程序会毫不客气地崩溃:

    Terminating app due to uncaught exception 'NSInvalidArgumentException', 
    reason: 'Invalid type in JSON write (NSObject)'
    
        1   libobjc.A.dylib  0x00007fff50b97b20 objc_exception_throw + 48
        2   Foundation       0x00007fff2580cb68 -[_NSJSONWriter writeRootObject:toStream:options:error:] + 0
        3   Foundation       0x00007fff2580ff03 ___writeJSONObject_block_invoke + 371
    [and so on]
    

    不是一个NSError。这是一个 NSException。它们是完全不同的东西,基本上这意味着我们的程序崩溃了。这不是错误的抛出和捕获;是猝死。

    这就是documentation 警告我们会发生的事情。如果我们提供一个无法转换为 JSON 的对象,则此方法 dataWithJSONObject:options:error 不会按顺序失败。它反而崩溃了:

    如果obj 不会生成有效的 JSON,则会引发异常。此异常在解析之前被抛出,表示编程错误,而不是内部错误。 应该在使用isValidJSONObject:调用此方法之前检查输入是否会生成有效的JSON

    (斜体是我的。)

    因此,发现问题并防止崩溃的正确方法是致电isValidJSONObject:first。这个方法,dataWithJSONObject:options:error,不会捕捉到这种问题,只返回一个 NSError;它会杀死整个程序。

    这就是发生在身上的事。您对try JSONSerialization.data(withJSONObject:) 的呼叫没有 throw。它只是崩溃了。如果您在实际的 Swift 应用程序中自行尝试,您会看到:

    override func viewDidLoad() {
        super.viewDidLoad()
        do {
            try testCreateJSONFrominValidDictionaryThrows()
        } catch {
            print("oops")
        }
    }
    func createBodyDataFrom(dictionary: [String: Any]) throws -> Data {
        let bodyData = try JSONSerialization.data(withJSONObject: dictionary, options: [])
        return bodyData
    }
    func testCreateJSONFrominValidDictionaryThrows() throws {
        let validDictionary: [String: Any] = [
            "object": NSObject()
        ]
        try createBodyDataFrom(dictionary: validDictionary)
    }
    

    我们运行应用程序,会发生什么?我们打印“oops”。相反,我们会遇到与 Objective-C 中完全相同的崩溃。

    这也是你的案例所发生的事情。我们从不投掷,因为我们反而坠毁了。测试失败报告了这一事实。你的testClient 从你下面崩溃了(你可以通过一些控制台的旋转来检测到这一点),并且测试本身作为失败返回,因为我们从未抛出一个 NSError。


    Cocoa 抛出 NSException 和返回 NSError 的区别在这里很重要。只有后者才算 Swift 抛出错误;前者是崩溃。参见示例

    How can I throw an NSError from a Swift class and catch it in an Objective-C class?

    Avoid handling all exceptions until they've reached a global handler

    How to catch NSUnknownKeyException in swift 2.2?

    【讨论】:

    • 在 Swift 中处理错误:在 Swift 中,此方法返回一个非可选的结果,并用 throws 关键字标记以指示它在失败的情况下抛出错误。您可以在 try 表达式中调用此方法并处理 do 语句的 catch 子句中的任何错误,如 Swift 编程语言中的错误处理和关于导入的 Cocoa 错误参数中所述。
    • 我实际上不知道这种方法在什么情况下会以良好的顺序返回 NSError 。因此,我无法解释关于这一点的无信息(可能是不正确的)文档。我只知道我们正在测试的情况,即无法生成有效 JSON 的值,不会以良好的顺序返回 NSError;它会生成一个 NSException ;这就是我的代码(和你的代码)所证明的,而且实际上是以一种相当省略的方式记录的。无论如何,我不会对文档设置太多存储。我靠对文档持怀疑态度来谋生。运行我给的代码,看看。
    猜你喜欢
    • 1970-01-01
    • 2020-02-14
    • 2018-12-31
    • 2021-01-08
    • 2013-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-01
    相关资源
    最近更新 更多