如何验证使用XCTAssert调用的类方法?

时间:2019-01-01 08:54:17

标签: 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点。但是,由于出现以下错误,当我尝试将其分配为已实现时,我无法访问我的期望

  

类声明不能结束值   在外部范围中定义的“ renderOAuthWebViewExpectation”

如何将其标记为已完成?

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

2 个答案:

答案 0 :(得分:1)

在模拟对象上创建一个属性,使其在您希望调用的方法内的值发生变化。然后,您可以使用XCTAssertEqual来检查道具是否已更新。

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

        class OAuthServiceMock: OAuthService {
            var renderOAuthWebViewExpectation: XCTestExpectation!
            var didCallRenderOAuthWebView = false

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

        let sut = OAuthServiceMock(apiClient: mockAPIClient)

        XCTAssertEqual(sut.didCallRenderOAuthWebView, false)
        sut.renderOAuthWebViewExpectation = renderOAuthWebViewExpectation

        sut.initAuthCodeFlow()
        waitForExpectations(timeout: 1) { _ in
            XCTAssertEqual(sut.didCallRenderOAuthWebView, true)
        }

    }

答案 1 :(得分:1)

  

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

我强烈建议您不要使用这种方法。如果您的SUT是子类的实例,则您的测试不是真正的测试OAuthService,而是真正的OAuthService模拟。

此外,如果我们将测试视为以下工具:

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

那么我会认为测试调用某个函数会调用另一个函数并不是一个好的测试。我知道那太难了,所以让我解开为什么会这样。

唯一要测试的是initAuthCodeFlow()在后​​台调用renderOAuthWebView(forService:, queryitems:)。它没有对被测系统的实际行为断言,也没有对直接产生或没有产生的输出断言。如果我要编辑renderOAuthWebView(forService:, queryitems:)的实现并添加一些在运行时崩溃的代码,则该测试不会失败。

像这样的测试并不能使代码库易于更改,因为如果您想更改OAuthService的实现,可以通过向renderOAuthWebView(forService:, queryitems:)添加参数或重命名{{ 1}}到queryitems中以匹配大小写,您必须同时更新生产代码和测试。换句话说,该测试将以您的重构方式-在不更改代码行为的情况下更改代码外观-不会带来任何额外好处。

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

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

我在这里猜测,但是我从您的测试中检查了initAuthCodeFlow()的正确性以及它得到renderOAuthWebView(forService:, queryitems:)类型作为输入的事实,我会说它会显示一些某种URL的Web视图,然后使用从Web视图接收到的OAuth令牌再次向给定的APIClient发出请求?

测试与APIClient的交互的一种方法是对要调用的预期端点进行断言。您可以使用OHHTTPStubs之类的工具进行操作,也可以使用APIClient的自定义测试记录来记录请求得到的请求并允许您检查请求。

对于Web视图的表示,您可以为其使用委托模式,并设置一个符合委托协议的测试双重记录,该协议记录是否调用了它。或者,您可以在更高级别进行测试,然后检查正在运行测试的URLSession,以查看根视图控制器是否是具有Web视图的控制器。

归根结底,这都是权衡问题。您采用的方法没有错,它只是针对声明代码实现而不是其行为进行了更多优化。我希望通过这个答案,我可以展示出另一种优化方式,一种偏向行为。以我的经验,这种测试方式在中长期证明更为有用。