Realm 使用Entwine测试基于领域的发布服务器时出现意外行为

Realm 使用Entwine测试基于领域的发布服务器时出现意外行为,realm,swift5,xctest,combine,Realm,Swift5,Xctest,Combine,我正在修补一个应用程序的持久性中间件,该应用程序使用了类似Redux的体系结构,使用Realm作为后端。数据库查询的结果应由联合发布服务器发出 尽管Realm现在支持Combine并提供FrozenCollection类型来管理线程和并发相关的错误,但有人建议我使用DTO(结构)使事情尽可能可预测。我已经编写了一个测试,它使用Entwine来确保将Realm FrozenCollection转换为DTO的行为符合预期,但是TestableSubscriber在时间索引0接收到一个空数组,而实际上

我正在修补一个应用程序的持久性中间件,该应用程序使用了类似Redux的体系结构,使用Realm作为后端。数据库查询的结果应由联合发布服务器发出

尽管Realm现在支持Combine并提供FrozenCollection类型来管理线程和并发相关的错误,但有人建议我使用DTO(结构)使事情尽可能可预测。我已经编写了一个测试,它使用Entwine来确保将Realm FrozenCollection转换为DTO的行为符合预期,但是TestableSubscriber在时间索引0接收到一个空数组,而实际上它应该接收三个输入(t=0时的初始空数组,t=100时的测试集为3个元素,t=200时的第二个测试集为3个元素)

我已经恢复到使用预期进行测试,以及使用print()调试发布服务器,并获得预期的输出(总共发出6个元素,转换为DTO正常工作),因此我假设我做错了什么,尽管我猜可能存在外部错误

目前正在使用Xcode12、iOS14。代码如下:

中间件(SUT)

<代码>导入基础 进口联合收割机 导入RealmSwift 最终类持久化中间件{ private var cancelables=Set 2020-10-03 15:49:39.231129+0100 SwiftRex ToDo持续存在[14502:643297]设置 2020-10-03 15:49:39.232989+0100 SwiftRex ToDo持续[14502:643297]拆除 测试用例“-[SwiftRex_ToDo_PersistedTests.SwiftRex_ToDo_PersistedTests TestCorrectNumberOfObjectsStoredRealm]”已通过(0.067秒)。 测试用例“-[SwiftRex_ToDo_PersistedTests.SwiftRex_ToDo_PersistedTests Tests MiddlewarePublisherUsingEntWine]”已启动。 2020-10-03 15:49:39.262702+0100 SwiftRex ToDo持续存在[14502:643297]设置 2020-10-03 15:49:39.264956+0100 SwiftRex ToDo持久化[14502:643474][]nw_协议获取图像块调用dlopen libquic失败 TestSequence(内容:[(0,.subscribe),(0,.input([]))] 脱硝 2020-10-03 15:49:39.267422+0100 SwiftRex ToDo持续[14502:643297]拆除 测试用例“-[SwiftRex_ToDo_PersistedTests.SwiftRex_ToDo_PersistedTests Tests MiddlewarePublisherUsingEntWine]”通过(0.033秒)。 测试用例“-[SwiftRex_ToDo_PersistedTests.SwiftRex_ToDo_PersistedTests Tests MiddlewarePublisherUsingExpection]”已启动。 2020-10-03 15:49:39.272248+0100 SwiftRex ToDo持续存在[14502:643297]设置 [14502:643297][SwiftRex_ToDo________________________________________________________________________________________________(id:4,名称:“烹饪晚餐”),SwiftRex_ToDo_.ToDo.DTO(id:5,名称:“支付账单”)] 脱硝 2020-10-03 15:49:39.275235+0100 SwiftRex ToDo持续[14502:643297]拆除 测试用例“-[SwiftRex_ToDo_PersistedTests.SwiftRex_ToDo_PersistedTests TestMiddleWarePublisherUsingExpection]”已通过(0.008秒)。 测试套件“SwiftRex_ToDo_PersistedTests”于2020-10-03 15:49:39.277通过。 在0.108(0.109)秒内执行了3次测试,0次失败(0次意外) 测试套件“SwiftRex ToDo PersistedTests.xTest”于2020-10-03 15:49:39.277通过。 在0.108(0.110)秒内执行了3次测试,0次失败(0次意外) 测试套件“所有测试”于2020-10-03 15:49:39.292通过。
执行了3个测试,在0.108(0.126)秒内0次失败(0次意外)

我在使用EntwineTest测试CoreData Combine包装器时遇到了类似的问题。我注意到在您的方法
func allToDos(…){…}
中将调度程序设置为
.receive(on:DispatchQueue.main)
我可以想象这是一个问题,因为Entwine的
TestScheduler
不是线程安全的:
import Foundation
import Combine

import RealmSwift

final class PersistenceMiddleware {
    private var cancellables = Set<AnyCancellable>()
    private var subject = CurrentValueSubject<[ToDo.DTO], Never>([])
    
    func allToDos(in realm: Realm = try! Realm()) -> AnyPublisher<[ToDo.DTO], Never> {
        realm.objects(ToDo.self)
            .collectionPublisher
//            .print()
            .assertNoFailure()
            .freeze()
            .map { item in
                item.map { $0.convertToDTO() }
            }
//            .print()
            .receive(on: DispatchQueue.main)
            .subscribe(subject)
            .store(in: &cancellables)
            
        return subject.eraseToAnyPublisher()
    }
    
    
    deinit {
        print("Deinit")
        cancellables = []
    }
}
import XCTest
import Combine

import RealmSwift
import EntwineTest

@testable import SwiftRex_ToDo_Persisted

class SwiftRex_ToDo_PersistedTests: XCTestCase {
    private var realm: Realm?
    
    private var testSet1: [ToDo] {
        [
            ToDo(name: "Mow lawn"),
            ToDo(name: "Wash car"),
            ToDo(name: "Clean windows")
        ]
    }
    
    private var testSet2: [ToDo] {
        [
            ToDo(name: "Walk dog"),
            ToDo(name: "Cook dinner"),
            ToDo(name: "Pay bills")
        ]
    }
    
    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
        realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: UUID().uuidString))
    }
    
    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        realm = nil
    }
    
    func testCorrectNumberOfObjectsStoredInRealm() {
        realm!.addForTesting(objects: testSet1)
        XCTAssertEqual(realm!.objects(ToDo.self).count, 3)
        
        realm!.addForTesting(objects: testSet2)
        XCTAssertEqual(realm!.objects(ToDo.self).count, 6)
    }
    
    func testMiddlewarePublisherUsingEntwine() {
        let middleware = PersistenceMiddleware()
        let scheduler = TestScheduler()
        let subscriber = scheduler.createTestableSubscriber([ToDo.DTO].self, Never.self)

        middleware.allToDos(in: self.realm!)
            .subscribe(subscriber)
        
        scheduler.schedule(after: 100) { self.realm!.addForTesting(objects: self.testSet1) }
        scheduler.schedule(after: 200) { self.realm!.addForTesting(objects: self.testSet2) }

        scheduler.resume()

        print("\(subscriber.recordedOutput)")
    }
    
    func testMiddlewarePublisherUsingExpectation() {
        let middleware = PersistenceMiddleware()
        var cancellables = Set<AnyCancellable>()
        let receivedValues = expectation(description: "received expected number of published objects")
        
        middleware.allToDos(in: realm!)
            .sink { result in
                if result.count == 6 {
                    NSLog(result.debugDescription)
                    receivedValues.fulfill()
                }
            }
            .store(in: &cancellables)

        realm!.addForTesting(objects: testSet1)
        realm!.addForTesting(objects: testSet2)

        waitForExpectations(timeout: 1, handler: nil)
    }
    
}