【问题标题】:RxSwift/RxTest How to test async function with Observable returnRxSwift/RxTest 如何使用 Observable 返回测试异步函数
【发布时间】:2022-01-20 09:45:36
【问题描述】:

我对 RxSwift 很陌生,我正在尝试创建一些单元测试。在这种情况下,我想测试从实时数据库 Firebase 获取对象是否正确发生。

func getAllPosts() -> Observable<[PostItem]> {
      ref = Database.database().reference()
        return Observable.create { observer -> Disposable in
        
        self.ref.child("Posts").observe(.value) { (snapshot) in
            var postsList:[PostItem] = []
            for child in snapshot.children {
                let snap = child as! DataSnapshot
                
            let postDict = snap.value as! [String: Any]
                let postAux = PostItem(id: snap.ref.key ?? "", authorId: postDict["authorId"] as? String  ?? "", name: postDict["name"] as? String  ?? "", content: postDict["content"] as? String  ?? "", createAt: postDict["createAt"] as? String  ?? "")
            postsList.append(postAux)
            }
            observer.onNext(postsList)
        }
        
    return Disposables.create {}
    }
}

问题是firebase的返回是异步的,我尝试测试的方式在返回之前完成。

func testFetchPosts() throws {
    
    let newsAPIService = NewsAPIService()
    let posts = newsAPIService.fetchNewsFromAPI()
    
    XCTAssertNil(posts, "The posts is nil")
}

Obs:我尝试使用 XCTest 期望,但我不知道是否实现不正确或者它是否真的不起作用

谢谢!

【问题讨论】:

  • 副作用(即 Firebase)不应出现在单元测试中。假设 Firebase 工作正常。
  • 当您在一次性操作中无事可做时,请使用Disposables.create() 而不是Disposables.create { }。前者将返回一个静态的 nil-disposable 对象,而后者将返回一个新创建的对象。但在这种情况下,您可能应该使用ref 来关闭observe(.value)。有ref.cancel()ref.stop() 方法吗?

标签: swift unit-testing rx-swift xctest rxtest


【解决方案1】:

我建议您将逻辑移出 Observable.create 以便您可以独立测试它。像这样的:

func getAllPosts() -> Observable<[PostItem]> {
    return Observable.create { observer in
        // note that you don't have to store this value. The disposable will hold it for you.
        let ref = Database.database().reference()
        // the `handleSnapshot(observer:)` function is defined below.
        ref.child("Posts").observe(.value, handleSnapshot(observer: observer))
        // make sure you turn off the observation on dispose.
        return Disposables.create { ref.stop() }
    }
}

您的逻辑应该在一个单独的高阶函数中:

// note that this is a free function. It should not be defined inside any class or struct.
// I'm guessing on the `Snapshot` type. I don't use Firebase.
func handleSnapshot(observer: AnyObserver<[PostItem]>) -> (Snapshot) -> Void {
    { snapshot in
        var postsList: [PostItem] = []
        for child in snapshot.children {
            let snap = child as! DataSnapshot

            let postDict = snap.value as! [String: Any]
            let postAux = PostItem(id: snap.ref.key ?? "", authorId: postDict["authorId"] as? String  ?? "", name: postDict["name"] as? String  ?? "", content: postDict["content"] as? String  ?? "", createAt: postDict["createAt"] as? String  ?? "")
            postsList.append(postAux)
        }
        observer.onNext(postsList)
    }
}

测试这个功能很简单:

func testShapshotHandler() throws {
    let scheduler = TestScheduler(initialClock: 0)
    let observer = scheduler.createObserver([PostItem].self)
    let snapshot = Snapshot(children: [Child()]) // create a snapshot object here
    let sut = handleSnapshot(observer: observer.asObserver())

    sut(snapshot)

    // assumes `PostItem` is Equatable
    XCTAssertEqual(observer.events, [.next(0, [PostItem(id: "", authorId: "", name: "", content: "", createAt: "")])])
}

如果您对高阶函数感到不舒服,您可以这样做:

func getAllPosts() -> Observable<[PostItem]> {
    return Observable.create { observer in
        let ref = Database.database().reference()
        ref.child("Posts").observe(.value) { snapshot in
            observer.onNext(handle(snapshot: snapshot))
        }
        return Disposables.create { ref.stop() }
    }
}

func handle(snapshot: Snapshot) -> [PostItem] {
    var postsList: [PostItem] = []
    for child in snapshot.children {
        let snap = child as! DataSnapshot

        let postDict = snap.value as! [String: Any]
        let postAux = PostItem(id: snap.ref.key ?? "", authorId: postDict["authorId"] as? String  ?? "", name: postDict["name"] as? String  ?? "", content: postDict["content"] as? String  ?? "", createAt: postDict["createAt"] as? String  ?? "")
        postsList.append(postAux)
    }
    return postsList
}

现在测试逻辑变得更加容易了。您不再需要测试调度程序或观察员。

【讨论】:

    猜你喜欢
    • 2019-12-19
    • 2021-09-03
    • 2020-11-22
    • 1970-01-01
    • 2019-02-07
    • 1970-01-01
    • 1970-01-01
    • 2018-06-27
    • 2018-01-03
    相关资源
    最近更新 更多