美文网首页iOS精品工作生活
基于Moya + RxSwift + ReactorKit 框架

基于Moya + RxSwift + ReactorKit 框架

作者: 天外丶飞仙 | 来源:发表于2019-07-03 16:42 被阅读0次

    在这个架构下我们主要讨论两个模块的单元测试,一个是网络模块,一个Reactor模块。

    1.网络层单元测试

    做网络请求测试时,我们希望给定一个测试数据时,就能同步返回这个数据。不需要异步的去服务器上获取。Moya框架提供了一个SampleData来专门用来单元测试使用,就不需要我们去Mock一个网络对象了。

    在定义的Moya的Target文件里面设置sampleData属性。

    var sampleData: Data {
        switch self {
        case .getAllProducts:
            return "".data(using: .utf8)!
        default:
            return "".data(using: .utf8)!
        }
    }
    

    然后设置一下MoyaProvider的stubClosure。StubBehavior是一个枚举值。

    • .never没有Stub,
    • .immediate 同步返回SampleData里面设置的数据,
    • .delayed(seconds:) 延迟时间返回SampleData里面设置的数据

    我们这里当然设置成MoyaProvider.immediatelyStub,发起Request之后就会立即返回sampleData里面的数据。

    let provider = MoyaProvider<NetworkTarget>(stubClosure: MoyaProvider.immediatelyStub)
    provider.rx.request(target)
    

    接下来我们就可以在单元测试文件里面写测试Case了。

    为了方便测试我们写了个服务层,例如HomeService就是关于Home界面的网络请求。测试home界面相关网络请求我们只需要测试HomeService这个类就可以了。

    比如我们想测试一个404错误请求的Case:

    func testToError_notFound() {
    
        //endpointClosure 自定义成我们想测试404error
        let endpointClosure = { (target: NetworkTarget) -> Endpoint in
            let url = URL(target: target).absoluteString
            return Endpoint(url: url, sampleResponseClosure: {.networkError(NSError(domain: "not fount", code: 404, userInfo: nil))}, method: target.method, task: target.task, httpHeaderFields: target.headers)
        }
        //创建一个立即返回Data的Provider
        let provider = MoyaProvider<NetworkTarget>(endpointClosure: endpointClosure,stubClosure: MoyaProvider.immediatelyStub)
        let netwoking = Network(provider: provider)
        var netError: NetworkError? = nil
        ServiceManager(networking: netwoking)
            .homeService
            .getAllProducts(page: 0)
            .subscribe(onSuccess: { (weatherData) in
                
            }, onError: { (error) in
                  //拿到返回的错误信息
                netError = NetworkError(error: error)
            })
            .disposed(by: disposeBag)
        
        //与预期的结果做对比
        XCTAssertEqual(netError, NetworkError.notFound)
    }
    

    RxBlocking

    Rx提供一个RxBlocking框架,专门用来做单元测试,RxBlocking将阻塞当前线程一直到观察者序列(observable)终止,toBlocking()就是RxBlocking提供的一个方法,它可以把原始的Observable变成一个BlockingObservable。这个BlockingObservable可以阻断当前线程,让我们用它提供的方法等待特定的事件发生。其中常用的三个方法是:

    • toArray()把Observable<T>中发生的所有事件,转换成一个[T]。这个方法只适用于有限序列,我们就可以用数组的形式观察到Observable中的所有值;
    • first(),得到Observable中第一个事件的值;
    • last(),得到Observable中最后一个事件的值;

    用toBlocking的方式写一个返回正确Data的Case:

    func testData_allProductInfos() {
    
        //endpointClosure 自定义成我们相反会的正确data
        let endpointClosure = { (target: NetworkTarget) -> Endpoint in
            let url = URL(target: target).absoluteString
            return Endpoint(url: url, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, task: target.task, httpHeaderFields: target.headers)
        }
        let provider = MoyaProvider<NetworkTarget>(endpointClosure: endpointClosure,stubClosure: MoyaProvider.immediatelyStub)
        let netwoking = Network(provider: provider)
        var response:[ProductInfo]?
        do {
            response = try ServiceManager(networking: netwoking)
                .homeService
                .getAllProducts(page: 0)
                .toBlocking()
                .first()
        } catch  {
                
        }
        
        //预期的数据模型
        let data = CommonTools.shareInstance.loadDataFromBundle(ofName: "AllProductInfo", ext: "json")
        let dictionary = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
        let mesageDic = dictionary!["data"] as? [[String: Any]]
        let models = Mapper<ProductInfo>().mapArray(JSONObject: mesageDic)
        
        //用返回的结果与预期的数据模型做对比
        XCTAssertEqual(response, models)
    }
    

    2.对Reactor单元测试

    项目中我们把业务逻辑代和数据都放到Reactor中,所以当我们想测试一个模块时,只需要测试它的Reactor。比如想要测试Home界面,我们直接测试HomeReactor。

    我们以PeiPeiPay项目首页作为一个例子:

    EV0KTH.png

    如图所示我们在HomeReactor中会把网络请求的模型数据转换为Cell需要显示的数据:

    //cell直接显示的数据模型
    struct GoodListCellData: Equatable {
        
        var name: String?
        var imageUrl: String?
        var info: String?
        var salePrice: String?
        var repertory: String?
        var originalPrice: NSAttributedString?
        
        static func == (lhs: GoodListCellData, rhs: GoodListCellData) -> Bool {
            return lhs.name == rhs.name
                    && lhs.imageUrl == rhs.imageUrl
                    && lhs.info == rhs.info
                    && lhs.salePrice == rhs.salePrice
                    && lhs.repertory == rhs.repertory
                    && lhs.originalPrice == rhs.originalPrice
        }
    }
    

    这样的话我们只需要测试GoodListCellData的数据是否正确就能确定cell上显示的数据是否正确了。

    首先自定义了一个Json数据

    {
        "data" : [{
                        "name" : "黑巧克力",
                        "image_url" : "https://s2.ax1x.com/2019/04/19/Epj1HI.png",
                        "item_code" : "1",
                        "sale_price" : 20.1,
                        "cost_price" : 30.1,
                        "count" : 20,
                        "note" : "好吃又好玩",
                        "category" : "食品",
                  }]
    }
    

    Tests文件的setUp发起网络请求

    override func setUp() {
        super.setUp()
        let provider = MoyaProvider<NetworkTarget>(stubClosure: MoyaProvider.immediatelyStub)
        let networking = Network(provider: provider)
        reactor = HomeViewReactor(serviceManager: ServiceManager(networking: networking))
        reactor.action.onNext(.downRefresh(searchName: ""))
    }
    

    然后我们就能写测试Case来验证Reactor里面的GoodListCellData数据和我们预期的json数据是否一致。

    //验证商品名
    func testAllProductItems_name() {
        
        XCTAssertEqual(reactor.currentState.goodListSectionModel[0].data[0].name, "黑巧克力")
        
    }
    
    //验证售价
    func testAllProductItems_salePrice() {
        
        XCTAssertEqual(reactor.currentState.goodListSectionModel[0].data[0].salePrice, "售价:20.1")
        
    }
    
    //验证原价
    func testAllProductItems_originalPrice() {
        
        let original = CommonTools.shareInstance.addlineToLabelText(text: "原价:30.1")
        XCTAssertEqual(reactor.currentState.goodListSectionModel[0].data[0].originalPrice, original)
        
    }
    

    在此框架下我们如果对所有Server和Reactor都进行了单元测试,其实已经能覆盖大部分的测试。

    Eeam0e.png EeaQfI.png

    使用Xcode可以查看测试覆盖率

    编写好测试用例之后,我们来看如何查看这些用例覆盖的代码范围。

    • 选择Test Scheme;
    • 切换到Options tab;
    • 选中Gather coverage for;
    • 切换到some targets;
    • 在下面的Targets列表中,添加测试Target;

    然后就能看的的测试覆盖率

    Eea1pt.png

    以上就是对现有框架下UnitTest的简单实践。

    相关文章

      网友评论

        本文标题:基于Moya + RxSwift + ReactorKit 框架

        本文链接:https://www.haomeiwen.com/subject/wirchctx.html