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

问题描述 投票:0回答:2

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

  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无论如何都会产生失败的测试*

swift unit-testing tdd xctestcase
2个回答
2
投票

在模拟上创建一个属性,在您希望调用的方法中改变它的值。然后,您可以使用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
投票

我希望创建一个本地子类OAuthService,将其分配为我的sut并调用像sut.initAuthCodeFlow()这样的东西然后断言我的期望已经完成。

我强烈反对你不使用这种方法。如果您的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提供某种Web视图,然后可以使用从Web视图收到的OAuth令牌向给定的APIClient发出另一个请求吗?

测试与APIClient的交互的一种方法是对要调用的预期端点进行断言。您可以使用像OHHTTPStubs这样的工具或URLSession的自定义测试双精度来记录它获得的请求并允许您检查它们。

至于Web视图的显示,您可以使用委托模式,并设置符合委托协议的测试双精度,该协议记录是否被调用。或者您可以在更高级别进行测试并检查运行测试的UIWindow,以查看根视图控制器是否是具有Web视图的控制器。

在一天结束时,所有这些都需要权衡利弊。您采用的方法没有错,它只是优化了断言代码实现而不是其行为。我希望通过这个答案,我展示了一种不同的优化,一种偏向于行为。根据我的经验,这种测试方式在中长期内证明更有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.