swift - 如何使用 XCTAssert 验证类方法是否被调用?

标签 swift unit-testing tdd xctestcase

我有一个服务类,我想断言两件事

  1. 调用了一个方法
  2. 将正确的参数传递给该方法

这是我的课

protocol OAuthServiceProtocol {
    func initAuthCodeFlow() -> Void
     func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void
}

class OAuthService: OAuthServiceProtocol {

    fileprivate let apiClient: APIClient

    init(apiClient: APIClient) {
        self.apiClient = apiClient
    }

    func initAuthCodeFlow() -> Void {

    }

    func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void {

    }
}

这是我的测试

class OAuthServiceTests: XCTestCase {
    var mockAPIClient: APIClient!
    var mockURLSession: MockURLSession!
    var sut: OAuthService!

    override func setUp() {
        mockAPIClient = APIClient()
        mockAPIClient.session = MockURLSession(data: nil, urlResponse: nil, error: nil)
        sut = OAuthService(apiClient: mockAPIClient)
    }

    func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
        let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")

        class OAuthServiceMock: OAuthService {
            override func initAuthCodeFlow() -> Void {

            }

            override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
                renderOAuthWebViewExpectation.fulfill()
            }
        }
    }
}

我希望创建一个 OAuthService 的本地子类,将其分配为我的 sut 并调用类似 sut.initAuthCodeFlow() 的东西,然后断言我的期望实现了。

我相信这应该满足第 1 点。但是,当我尝试将其分配为已实现时,我无法访问我的期望,因为出现以下错误

Class declaration cannot close over value 'renderOAuthWebViewExpectation' defined in outer scope

如何将其标记为已完成?

我正在遵循 TDD 方法,所以我知道我的 OAuthService 无论如何都会在此时产生失败的测试*

最佳答案

I was hoping to create a local sub class of OAuthService, assign that as my sut and call something like like sut.initAuthCodeFlow() and then assert that my expectation was fulfilled.

我强烈建议您不要使用这种方法。如果您的 SUT 是子类的一个实例,那么您的测试并不是真正测试 OAuthService,而是 OAuthService mock。

此外,如果我们将测试视为一种工具:

  • 防止代码更改时出现错误
  • 帮助重构和维护代码

那么我会争辩说,测试调用某个函数调用另一个函数并不是一个好的测试。我知道这很苛刻,所以让我来解释一下为什么会这样。

它唯一测试的是 initAuthCodeFlow() 在后台调用 renderOAuthWebView(forService:, queryitems:)。它对被测系统的实际行为 以及它是否直接生成的输出没有任何断言。如果我要编辑 renderOAuthWebView(forService:, queryitems:) 的实现并添加一些会在运行时崩溃的代码,这个测试就不会失败。

像这样的测试无助于保持代码库易于更改,因为如果您想更改 OAuthService 的实现,可能通过向 renderOAuthWebView(forService: , queryitems:) 或通过将 queryitems 重命名为 queryItems 以匹配大小写,您必须同时更新生产代码和测试代码。换句话说,测试将妨碍您进行重构 - 改变代码的外观而不改变代码的行为方式 - 没有任何额外的好处。

那么,应该如何以一种防止错误并帮助快速移动的方式测试 OAuthService?诀窍在于测试行为而不是实现。

OAuthService 应该做什么initAuthCodeFlow() 不返回任何值,因此我们可以检查直接输出,但我们仍然可以检查间接输出、副作用。

我在这里猜测,但我从你的测试中检查了 renderOAuthWebView(forService:, queryitems:) 我会得到一个 APIClient 输入类型我会说它会为某个 URL 呈现某种 WebView ,然后向给定的 APIClient 发出另一个请求,可能使用从 WebView 接收到的 OAuth token ?

测试与 APIClient 交互的一种方法是对要调用的预期端点进行断言。您可以使用 OHHTTPStubs 之类的工具来完成或者使用您的 URLSession 自定义测试替身,记录它收到的请求并允许您检查它们。

对于webview的呈现,可以使用delegate patter,设置一个符合delegate协议(protocol)的test double记录是否被调用。或者您可以在更高级别进行测试并检查运行测试的 UIWindow 以查看 Root View Controller 是否是具有 Web View 的 Controller 。

归根结底,一切都是权衡取舍的问题。您采用的方法并没有错,它只是针对断言代码实现而不是其行为进行了更多优化。我希望通过这个答案,我展示了一种不同类型的优化,一种偏向于行为的优化。根据我的经验,从中长期来看,这种测试方式更有帮助。

关于swift - 如何使用 XCTAssert 验证类方法是否被调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53994172/

相关文章:

tdd - 测试驱动开发适合初学者吗?

reactjs - 如何模拟模块中的特定功能

swift - Realm Swift 如何在结果更新时得到通知

Swift 和 Realm : getting Thread 1: signal SIGABRT when add objects in list

javascript - Jasmine 可以在对象上创建新属性并随后删除它们吗?

unit-testing - 是否有一个MSTest等效于NUnit的显式属性?

unit-testing - 测试内部类

swift - 防止平移多个可平移的表格 View 单元格

swift - 合并两个字典的通用函数

ruby - 在 RSpec 中期待 Lambda