繁体   English   中英

如何在Swift中使用OCMock模拟来自AFNetworking的响应?

[英]How to mock response from AFNetworking with OCMock in Swift?

这是我获取网络客户端实例的方式:

let networkClient = DBNetworkClient(baseURL: NSURL(string: "http://mysite.pl/api"))

我也有一种方法:

func citiesWithParameters(parameters: [String: String], completionBlock: DBSearchOptionHandler) {

    GET("cities", parameters: parameters, success: { operation, response in

        if let error = NSError(response: response) {
            completionBlock([], error)
        } else {
            let cities = DBCity.parseCitiesWithDictionary(response as! NSDictionary)
            completionBlock(cities, nil)
        }

        }) { operation, error in

            completionBlock([], error)
    }
}

这就是我所谓的方法:

networkClient.citiesWithParameters(parameters, completionBlock: { cities, error in 
    //do sth 
})

这样,我传递了一些参数,并从服务器获得了REAL响应。 当我要求时,我想嘲笑那个回应。 这个怎么做?

func testCities() {

    let mockNetworkClient = OCMockObject.mockForClass(DBNetworkClient.classForCoder())        


    //what should I perform here to be able to do sth like this:

    let expectation = expectationWithDescription("")

    mockNetworkClient.citiesWithParameters(["a": "b"]) { cities, error in
        expectation.fulfill()
        XCTAssertNotNil(cities)
        XCTAssertNil(error) //since I know what is the response, because I mocked this
    }

    waitForExpectationsWithTimeout(10, handler: nil)
}

这就是在DBNetworkClient定义我的方法GET方式:

override func GET(URLString: String!, parameters: AnyObject!, success: ((AFHTTPRequestOperation!, AnyObject!) -> Void)!, failure: ((AFHTTPRequestOperation!, NSError!) -> Void)!) -> AFHTTPRequestOperation! {

    return super.GET(URLString, parameters: parameters, success: { (operation, response) in

        print("GET \(operation.request.URL)")
        print("GET \(response)")

        success(operation, response)

        }, failure: { (operation, error) in

            print("GET \(operation.request.URL)")
            print("GET \(operation.responseObject)")

            failure(operation, error)
    })
}

一旦我能够为我提供50英镑的赏金 ,将帮助我做到这一点。

不幸的是, 为AFNetworking编写模拟测试没有帮助。 它对我不起作用。

我没有使用Alamofire的经验,所以我不知道在哪里声明您的GET方法,但是您绝对应该在此方法中添加citiesWithParameters而不要使用citiesWithParameters

例如,如果在DBNetworkClient声明了DBNetworkClient

func testCities()
{
    //1
    class DBNetworkClientMocked: DBNetworkClient
    {
        //2
        override func GET(URLString: String!, parameters: AnyObject!, success: ((AFHTTPRequestOperation!, AnyObject!) -> Void)!, failure: ((AFHTTPRequestOperation!, NSError!) -> Void)!) -> AFHTTPRequestOperation! {

            //3
            success(nil, ["San Francisco", "London", "Sofia"])

        }
    }

    //4
    let sut = DBNetworkClientMocked()
    sut.citiesWithParameters(["a":"b"]) { cities, error in

        //5
        XCTAssertNotNil(cities)
        XCTAssertNil(error)

    }
}

那么这里发生了什么:

  1. 您定义的类是DBNetworkClient子类,因此按照您发布的文章中的描述为其创建“模拟”。 在该类中,我们将仅覆盖我们要更改的方法(存根),并使其他方法保持不变。 这以前是使用OCMock完成的,称为部分模拟。
  2. 现在,将其存入GET方法以返回特定数据,而不是服务器中的实际数据
  3. 您可以在此处定义返回存根服务器的内容。 可能是成功,失败等。
  4. 我们愿与嘲弄和存根现在,我们创作出如此称为S ubjectü的nDer 牛逼 EST(SUT)。 请注意,如果DBNetworkClient具有特定的构造函数,则必须调用此构造函数,而不是默认的构造函数-()。
  5. 执行要测试的方法。 在回调内部,我们放入了所有断言。

到现在为止还挺好。 但是,如果GET方法是Alamofire的一部分,则需要使用一种称为“依赖注入”的技术(您可以在Google上搜索以获得更多信息)。

因此,如果GET在另一个类中声明并且仅在citysWithParameters中引用,我们如何对它进行存根处理? 让我们看一下:

//1
class DBNetworkClient
{
    //2
    func citiesWithParameters(parameters: [String: String], networkWorker: Alamofire = Alamofire(), completionBlock: DBSearchOptionHandler) {

        //3
        networkWorker.GET("cities", parameters: parameters, success: { operation, response in

            if let error = NSError(response: response) {
                completionBlock([], error)
            } else {
                let cities = DBCity.parseCitiesWithDictionary(response as! NSDictionary)
                completionBlock(cities, nil)
            }

            }) { operation, error in

                completionBlock([], error)
        }

    }
}

func testCities()
{
    //4
    class AlamofireMock: Alamofire
    {
        override func GET(URLString: String!, parameters: AnyObject!, success: ((AFHTTPRequestOperation!, AnyObject!) -> Void)!, failure: ((AFHTTPRequestOperation!, NSError!) -> Void)!) -> AFHTTPRequestOperation! {

            success(nil, ["San Francisco", "London", "Sofia"])

        }
    }

    //5
    let sut = DBNetworkClient()
    sut.citiesWithParameters(["a":"b"], networkWorker: AlamofireMock()) { cities, error in

        //6
        XCTAssertNotNil(cities)
        XCTAssertNil(error)

    }
}
  1. 首先,我们必须稍微更改我们的citiesWithParameters才能接收另一个参数。 此参数是我们的依赖项注入。 是具有GET方法的对象。 在现实生活中的示例中,最好是仅使用协议,因为citiesWithParameters不需要知道的其他信息超过该对象能够发出请求的能力。
  2. 我已将networkWorker参数设置为默认值,否则您需要citiesWithParameters所有调用citiesWithParameters为新参数要求。
  3. 我们将所有实现保持不变,只是现在我们调用了注入的对象GET方法
  4. 现在回到测试中,我们这次将模拟Alamofire。 我们做了与上一个示例完全相同的操作,只是这次模拟的类是Alamofire而不是DBNetworkClient
  5. 当我们调用citiesWithParameters时,我们将citiesWithParameters对象作为networkWorker传递。 这样,我们的存根GET方法将被调用,我们将从“假”服务器中获得期望的数据。
  6. 我们的主张保持不变

请注意,这两个示例不使用OCMock,而是完全依靠Swift的力量! OCMock是一个很棒的工具,我们使用了很棒的动态语言-Objective-C。 但是,关于Swift的动态性和反思性几乎完全消失了。 这就是为什么即使在OCMock官方页面上我们也有以下声明:

会不会有用Swift编写的Swift模拟框架? 也许。 到目前为止,它看起来不太可能,因为模拟框架在很大程度上取决于对语言运行时的访问,而Swift似乎没有提供任何东西。

提供的两个实现中缺少的是验证调用GET方法。 我将这样保留它,因为这不是原始问题,您可以使用在模拟类中声明的布尔值轻松实现它。

更重要的一点是,我假设Alamofire中的GET方法是实例方法,而不是类方法。 如果不是这样,则可以在DBNetworkClient内部声明一个新方法,该方法只需调用Alamofire.GET并在citiesWithParameters内部使用该方法。 然后,您可以像第一个示例一样对这个方法进行存根,一切都会好起来的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM