在Swift中使用通用参数函数模拟类

时间:2020-05-12 15:03:17

标签: ios swift unit-testing mocking xctest

我正在尝试对简单的HttpClient行为进行单元测试。为此,我创建了GenericHttpClientInterface协议和实现该协议的具体类GenericHttpClient。

protocol GenericHttpClientInterface {
    func makeRequest<T: Decodable>(request: URLRequest) -> Observable<T>
}

class GenericHttpClient: GenericHttpClientInterface {
    func makeRequest<T: Decodable>(request: URLRequest) -> Observable<T> {
        return URLSession.shared.rx.data(request: request).jsonDecode(to: T.self)
    }
}

我想实现的是模拟该类:

class MockHttpClient: GenericHttpClientInterface {
    var invokedMakeRequestCount = 0
    var invokedMakeRequestParameters: (request: URLRequest, Void)?
    var stubbedMakeRequestResult: Observable<Any>!
    func makeRequest<T: Decodable>(request: URLRequest) -> Observable<T> {
        invokedMakeRequestCount += 1
        invokedMakeRequestParameters = (request, ())
        return stubbedMakeRequestResult as! Observable<T>;
    }
}

给我一​​个问题是我在嘲笑的方法内部有通用参数T,该参数是将请求解码为的类。我不知道这个参数,直到我调用此函数为止,基本上是在MockHttpClient类中为存储为我创建的makeRequest存根数据的属性创建的:

stubbedMakeRequestResult: Observable<Any>

,返回后,我试图将其强制转换为Observable类型。这给了我一个警告

Cast from 'Observable<Any>?' to unrelated type 'Observable<T>' always fails

因此

Thread 1: signal SIGABRT.

有什么想法要对这些数据进行存根吗?

创建SIGABRT的示例测试:

class GenericHttpTest: XCTestCase {
    
    var sut: Repository!
    var mockHttpClient: MockHttpClient!
    
    override func setUp() {
        mockHttpClient = MockHttpClient()
        sut = Repository(httpClient: mockHttpClient)
    }
        
    let test_mocked_data_stub = DataModelStruct(args: DataModelStruct.InsideModelStruct(foo1: "bar"))

    func test_should_return_mocked_data_from_mock_http_client() {
        mockHttpClient.stubbedMakeRequestResult = Observable.just(test_mocked_data_stub)
        
        let response = try! sut.getFooBar().toBlocking().first()

        XCTAssertEqual(response, test_mocked_data_stub)
    }
}

1 个答案:

答案 0 :(得分:0)

由于Swift中的泛型are invariant,除非Observable<Any>Observable<T>,否则T永远不会转换为Any。这就是导致崩溃的原因,因为当您将值分配给stubbedMakeRequestResult时,具体的Observable会转换为Observable<Any>,并且这里没有转折点。

为避免这种情况,您可以做的是将stubbedMakeRequestResult设为Any,因为这不会在幕后进行任何转换。一个小问题是您松开了类型安全性和推断,但是可以通过以下功能修复此存根:

class MockHttpClient: GenericHttpClientInterface {
    ...
    var stubbedMakeRequestResult: Any!
    
    func stubMakeRequestResult<T>(_ result: Observable<T>) {
        stubbedMakeRequestResult = result
    }
    ...
}