Not getting all expected events when subscribing to sequence with TestScheduler - swift

I'm trying to write an integration test for a Reactor in an app built with ReactorKit and Realm/RxRealm.
I'm having trouble using TestScheduler to simulate user actions and test the expected emitted states.
In a nutshell, my problem is this: I'm binding an action that will make my Reactor save an item to Realm, my Reactor also observes changes to this object in Realm, and I expect my Reactor to emit the new state of this item observed from Realm.
What I'm seeing is that my test does not get the emission of the newly saved object in time to assert its value, it's emitted after my test assertion runs.
There is a fair amount of code involved, but attempting to whittle it down into a self-contained example of what it all roughly looks like below:
struct MyObject {
var counter: Int = 0
}
class MyReactor: Reactor {
enum Action {
case load
case mutateState
}
enum Mutation {
case setObject(MyObject)
}
struct State {
var object: MyObject?
}
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .load:
return service.monitorObject().map(Mutation.setObject)
case .mutateState:
guard var myObject = currentState.object else { return .empty() }
myObject.counter += 1
return service.save(myObject).andThen(.empty())
}
}
func reduce(state: State, mutation: Mutation) -> Observable<State> {
var newState = state
switch mutation {
case let .setObject(object):
// Should be called twice in tests, once on load, once after mutateState action
newState.object = object
}
}
}
struct Service {
// There is always at least one default instance of `MyObject` in Realm.
func monitorObject() -> Observable<MyObject> {
return Observable
.collection(from: realm.objects(MyObject.self))
.map { $0.first! }
}
func save(_ object: MyObject) -> Completable {
return Completable.create { emitter in
try! realm.write {
realm.add(object, update: .modified)
}
emitter(.completed)
return Disposables.create()
}
}
}
class MyTest: QuickSpec {
var scheduler: TestScheduler!
var sut: MyReactor!
var disposeBag: DisposeBag!
var service: Service!
var config: Realm.Configuration!
override func spec() {
beforeEach {
config = Realm.Configuration(inMemoryIdentifier: UUID().uuidString)
scheduler = TestScheduler(initialClock: 0)
disposeBag = DisposeBag()
sut = MyReactor()
service = Service(realmConfig: config)
}
describe("when my reactor gets a mutateState action") {
it("should mutate state") {
scheduler.createHotObservable([
.next(1, Action.load),
.next(2, Action.mutateState),
])
.bind(to: sut.action)
.disposed(by: disposeBag)
let response = scheduler.start(created: 0, subscribed: 0, disposed: 1000) {
sut.state.map(\.object)
}
// Counter always equals 0
XCTAssertTrue(response.events.last!.value.element!!.counter == 1)
}
}
}
}
What I'm expecting to happen is my Reactor's state is set for a 2nd time, before the XCTAssertTrue is hit. What is actually happening is the assert is hit with the initially loaded state, and then, my reactor's state is set again.
I thought my problem might be related to schedulers. Something I tried was injecting the test scheduler into my Service and doing observeOn(testScheduler) on my monitorObject function. But I'm still observing the assert get hit before the reactor's state is set for the 2nd time. I'm also not sure if a nuance of RxRealm/Realm change set notifications is the cause - not sure how to verify whether that might be the case.
Hopefully the problem and question is clear. Thanks in advance for any help.

I decided attempting to write an integration test was more trouble than it was worth and probably not going to result in very useful tests anyway.

So you are trying to test to see if Realm works. I don't use Realm, but based on your description, it probably updates the object on an internal thread and then you get the emission on a subsequent cycle.
You can test it by using an XCTestExpectation. Here is documentation from Apple: https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations/testing_asynchronous_operations_with_expectations
Note however, that if something goes wrong in Realm and this test fails, there isn't anything you can do about it.

Related

Swift how to test async call wrapped in a function

Is there a way to wait for an async call to be finished when this call is wrapped in another method?
class Owner{
let dataManager = MockDataManager()
var data: String? = nil
func refresh() {
Task {
self.data = await dataManager.fetchData()
}
}
}
class MockDataManager {
var testData: String = "test"
func fetchData() async -> String {
testData
}
}
class OwnerTests: SKTestCase {
private var owner = Owner()
func testRefresh() {
owner.refresh()
XCTAssertEqual(owner.data, "test") // fail. the value is still nil
}
}
With callbacks, the tests used to work if everything under the hood was replaced with synchronous calls but here it looks like i am missing an operation to wait for a change to owner.data
Late to this party, but I agree with #Cristik regarding not changing the signature of a function just to accommodate testing. In the chat room conversation, #Cristik also pointed out a valid reason why a function can be set up to invoke an async function but yet not define its signature as async:
the Owner class may be in the nature of a view model (in an MVVM pattern) that exposes read-only observable/bindable (say #Published let) properties, that are bindable from (a) view(s), and the refresh function allows the view to request data update following a user event;
the refresh function isn't expected to return any data to the view when invoked, rather the view model (Owner) object will update the observable properties with the data returned while the views bound to (i.e. observing) the properties will be automatically updated.
In this case there's absolutely no need to mark the Owner.refresh() function as async and, thus forcing the view(s) to wrap their invocation of the refresh function in an async or Task (or .task modifier in SwiftUI) construct.
That said, I had similar situation and here's how I implemented the unit (not integration) test:
func testRefreshFunctionFetchesDataAndPopulatesFields() {
let expectation = XCTestExpectation(
description: "Owner fetches data and updates properties."
)
// `Owner` is the "subject under test", so use protocol-driven development
// and dependency injection to enable focusing on testing just the SUT
// unencumbered by peculiarities of the dependency
let owner = Owner(mockDataManager: DataManagerProtocol())
// Verify initial state
XCTAssertNil(owner.data)
owner.refresh()
let asyncWaitDuration = 0.5 // <= could be even less than 0.5 seconds even
DispatchQueue.main.asyncAfter(deadline: .now() + asyncWaitDuration) {
expectation.fulfill()
// Verify state after
XCTAssertEqual(owner.data, "someString")
}
wait(for: [expectation], timeout: asyncWaitDuration)
}
Hope this helps.
The fact that refresh detaches some async code to do its job, is an implementation detail, and your tests should not care about the implementation details.
Instead, focus on the behaviour of the unit. For example, in the scenario you posted, the expected behaviour is that sometime after refresh is called, owner.data should become "test". This is what you should assert against.
Your current test code follows the above good practice, only that, as you observed, it fails because it doesn't wait until the property ends up being set. So, try to fix this, but without caring how the async part is implemented. This will make your tests more robust, and your code easier to refactor.
One possible approach for validating the async update is to use a custom XCTestExpectation:
final class PropertyExpectation<T: AnyObject, V: Equatable>: XCTNSPredicateExpectation {
init(object: T, keyPath: KeyPath<T, V>, expectedValue: V) {
let predicate = NSPredicate(block: { _, _ in
return object[keyPath: keyPath] == expectedValue
})
super.init(predicate: predicate, object: nil)
}
}
func testRefresh() {
let exp = PropertyExpectation(object: owner, keyPath: \.data, expectedValue: "test")
owner.refresh()
wait(for: [exp], timeout: 5)
}
Alternatively, you can use a 3rd party library that comes with support for async assertions, like Nimble:
func testRefresh() {
owner.refresh()
expect(self.owner.data).toEventually(equal("test"))
}
As a side note, since your code is multithreaded, strongly recommending to add some synchronization in place, in order to avoid data races. The idiomatic way in regards to the structured concurrency is to convert your class into an actor, however, depending on how you're consuming the class from other parts of the code, it might not be a trivial task. Regardless, you should fix the data races conditions sooner rather than later.
I would like to contribute a solution for a more restricted situation where XCTestExpectation doesn't work, and that's when a view model is bound to the #MainActor, and you can't make every function call async (relying on property didSet). Waiting for expectations will also block the task in question, even a detached task won't help, the task will always execute after the test function. Storing and later awaiting the task solves the problem:
#MainActor
class ViewModel {
var task : Task<Void, Never>?
#Published var value1: Int = 0 {
didSet {
task = Task {
await update2()
}
}
}
#Published var value2: Int = 0
func update2() async {
value2 = value1 + 1
}
}
And then in the test:
func testExample() {
viewModel.value1 = 1
let _ = await viewModel.task?.result
XCTAssertEqual(viewModel.value2, 2)
}
func refresh() async {
self.data = await dataManager.fetchData()
}
then in the test await owner.refresh()
If you really need to wait synchronously for the async task, you can see this question Swift await/async - how to wait synchronously for an async task to complete?

XCTest: Wait for async call to finish in synchronous function [duplicate]

Is there a way to wait for an async call to be finished when this call is wrapped in another method?
class Owner{
let dataManager = MockDataManager()
var data: String? = nil
func refresh() {
Task {
self.data = await dataManager.fetchData()
}
}
}
class MockDataManager {
var testData: String = "test"
func fetchData() async -> String {
testData
}
}
class OwnerTests: SKTestCase {
private var owner = Owner()
func testRefresh() {
owner.refresh()
XCTAssertEqual(owner.data, "test") // fail. the value is still nil
}
}
With callbacks, the tests used to work if everything under the hood was replaced with synchronous calls but here it looks like i am missing an operation to wait for a change to owner.data
Late to this party, but I agree with #Cristik regarding not changing the signature of a function just to accommodate testing. In the chat room conversation, #Cristik also pointed out a valid reason why a function can be set up to invoke an async function but yet not define its signature as async:
the Owner class may be in the nature of a view model (in an MVVM pattern) that exposes read-only observable/bindable (say #Published let) properties, that are bindable from (a) view(s), and the refresh function allows the view to request data update following a user event;
the refresh function isn't expected to return any data to the view when invoked, rather the view model (Owner) object will update the observable properties with the data returned while the views bound to (i.e. observing) the properties will be automatically updated.
In this case there's absolutely no need to mark the Owner.refresh() function as async and, thus forcing the view(s) to wrap their invocation of the refresh function in an async or Task (or .task modifier in SwiftUI) construct.
That said, I had similar situation and here's how I implemented the unit (not integration) test:
func testRefreshFunctionFetchesDataAndPopulatesFields() {
let expectation = XCTestExpectation(
description: "Owner fetches data and updates properties."
)
// `Owner` is the "subject under test", so use protocol-driven development
// and dependency injection to enable focusing on testing just the SUT
// unencumbered by peculiarities of the dependency
let owner = Owner(mockDataManager: DataManagerProtocol())
// Verify initial state
XCTAssertNil(owner.data)
owner.refresh()
let asyncWaitDuration = 0.5 // <= could be even less than 0.5 seconds even
DispatchQueue.main.asyncAfter(deadline: .now() + asyncWaitDuration) {
expectation.fulfill()
// Verify state after
XCTAssertEqual(owner.data, "someString")
}
wait(for: [expectation], timeout: asyncWaitDuration)
}
Hope this helps.
The fact that refresh detaches some async code to do its job, is an implementation detail, and your tests should not care about the implementation details.
Instead, focus on the behaviour of the unit. For example, in the scenario you posted, the expected behaviour is that sometime after refresh is called, owner.data should become "test". This is what you should assert against.
Your current test code follows the above good practice, only that, as you observed, it fails because it doesn't wait until the property ends up being set. So, try to fix this, but without caring how the async part is implemented. This will make your tests more robust, and your code easier to refactor.
One possible approach for validating the async update is to use a custom XCTestExpectation:
final class PropertyExpectation<T: AnyObject, V: Equatable>: XCTNSPredicateExpectation {
init(object: T, keyPath: KeyPath<T, V>, expectedValue: V) {
let predicate = NSPredicate(block: { _, _ in
return object[keyPath: keyPath] == expectedValue
})
super.init(predicate: predicate, object: nil)
}
}
func testRefresh() {
let exp = PropertyExpectation(object: owner, keyPath: \.data, expectedValue: "test")
owner.refresh()
wait(for: [exp], timeout: 5)
}
Alternatively, you can use a 3rd party library that comes with support for async assertions, like Nimble:
func testRefresh() {
owner.refresh()
expect(self.owner.data).toEventually(equal("test"))
}
As a side note, since your code is multithreaded, strongly recommending to add some synchronization in place, in order to avoid data races. The idiomatic way in regards to the structured concurrency is to convert your class into an actor, however, depending on how you're consuming the class from other parts of the code, it might not be a trivial task. Regardless, you should fix the data races conditions sooner rather than later.
I would like to contribute a solution for a more restricted situation where XCTestExpectation doesn't work, and that's when a view model is bound to the #MainActor, and you can't make every function call async (relying on property didSet). Waiting for expectations will also block the task in question, even a detached task won't help, the task will always execute after the test function. Storing and later awaiting the task solves the problem:
#MainActor
class ViewModel {
var task : Task<Void, Never>?
#Published var value1: Int = 0 {
didSet {
task = Task {
await update2()
}
}
}
#Published var value2: Int = 0
func update2() async {
value2 = value1 + 1
}
}
And then in the test:
func testExample() {
viewModel.value1 = 1
let _ = await viewModel.task?.result
XCTAssertEqual(viewModel.value2, 2)
}
func refresh() async {
self.data = await dataManager.fetchData()
}
then in the test await owner.refresh()
If you really need to wait synchronously for the async task, you can see this question Swift await/async - how to wait synchronously for an async task to complete?

How to make a proper reactive extension on Eureka SelectableSection

This is my first question to the StackOverflow community so excuse me if I'm doing something wrong.
1. What I'm trying to achieve
Basically, I want to make a custom reactive wrapper around Eureka's SelectableSection class in order to observe the value of the selected row when it is changed. I'm thinking to get this data from the onSelectSelectableRow closure which is called every time a row is selected.
2. What I've tried to do for that
Actually, I've got this working but it's not a generic use of the custom wrapper, here is the example that works but only when I specify the row and its value type, for example ListCheckRow<Int>.
extension SelectableSection: ReactiveCompatible {}
extension Reactive where Base : SelectableSection<ListCheckRow<Int>> {
var selectedValue: Observable<Base.SelectableRow.Cell.Value?> {
return Observable.create { observer in
self.base.onSelectSelectableRow = {cell, row in
observer.onNext(row.value)
}
return Disposables.create {
observer.onCompleted()
}
}
}
}
This works fine and as I expected but when it comes to something more generic like the next code example, I get an error saying that: "Cannot assign to property: 'base' is a 'let' constant"
extension SelectableSection: ReactiveCompatible {}
extension Reactive where Base : SelectableSectionType {
var selectedValue: Observable<Base.SelectableRow.Cell.Value?> {
return Observable.create { observer in
self.base.onSelectSelectableRow = {cell, row in // Error: Cannot assign to property: 'base' is a 'let' constant
observer.onNext(row.value)
}
return Disposables.create {
observer.onCompleted()
}
}
}
}
Any help will be much appreciated, thanks. 🙏
The fundamental problem here is that SelectableSectionType is a protocol that isn't restricted to class types and Reactive assumes that Base is a class (or otherwise is not going to be modified by the observable creation.)
I think the most generic you can make this is something like:
extension Reactive {
func selectedValue<Row, T>() -> Observable<T?> where Base: SelectableSection<Row>, Row: SelectableRowType, T == Row.Cell.Value {
Observable.create { [base] observer in
base.onSelectSelectableRow = { cell, row in
observer.onNext(row.value) // this is problematic. See below.
}
return Disposables.create {
observer.onCompleted() // this is wrong. See below.
}
}
}
}
The biggest problem with the above though is that if you subscribe to the resulting Observable more than once or create more than one Observable using this computed property, all but the last subscription will silently fail. The simple way to fix this is to always remember to share any result but that's rather error prone.
The way to fix this would be to associate a Subject with each SelectableSection, but you can't modify the class, so what are we to do?
Here's a solution:
extension Reactive {
func selectedValue<Row, T>() -> Observable<T?> where Base: SelectableSection<Row>, Row: SelectableRowType, T == Row.Cell.Value {
Observable.create { [base] observer in
if let block = selectableSections.first(where: { $0.section === base }) {
let subject = block.subject as! PublishSubject<T?>
return Disposables.create(
block.disposable.retain(),
subject.subscribe(observer)
)
}
else {
let subject = PublishSubject<T?>()
let block = SelectableSectionBlock(
section: base,
subject: subject,
disposable: RefCountDisposable(disposable: Disposables.create {
selectableSections.removeAll(where: { $0.section === base })
})
)
base.onSelectSelectableRow = { cell, row in
subject.onNext(row.value)
}
selectableSections.append(block)
return Disposables.create(
block.disposable,
subject.subscribe(observer)
)
}
}
}
}
private struct SelectableSectionBlock {
let section: Section
let subject: Any
let disposable: RefCountDisposable
}
private var selectableSections = [SelectableSectionBlock]()
The selectableSections array stores a Subject and RefCountDisposable for each SelectableSection.
Whenever an Observable is created, or subscribed to...
if it's the first time working with this section, it will create a Subject and RefCountDisposable assign the onSelectSelectableRow to send a next event to the subject and store the subject in the array.
otherwise it will find the subject and disposable associated with this Section and retain the disposable.
Once it has the subject and disposable from above, it will subscribe the new observer to the subject and return a new Disposable that will remove that subscription and decrement the ref-count when the time comes.
Yes this is quite a bit more complex than the simple assignment case, but it's the right thing to do.
As for calling onCompleted() inside the disposable closure. By the time the closure is called, the observer has already emitted an onCompleted/onError event, or the observer has stopped listening to the observable. So this event will never be seen.

Is there a way to check if downstream handles a event a publisher publishes in Combine?

I have an event bus that publishes events happening over a custom text field(a chat message input box), like attachMediaEvent, sendMessageEvent, selectTextEvent. I want to provide provide default actions for the events, but only in case downstream doesn't handle it(kind of like how one can provide protocol default implementation). Is there a way to check if the downstream handles the event and handle it if doesn't?
I know that I'm being loose with the phrase handling the event but it was intentional and I would like any sort of solution for it. And I'm not adding any code, but I can do so, if needed. It is a really simple publisher that pushes events, anyhow.
EDIT:
Ok, I dug deeper in SO and I started to get the idea that I had to implement custom operator. But I have hit the exact same roadblock. I have no idea how a downstream can speak to the upstream about how it has handled its values. Although I tried to create a protocol(EventConsumer) that one could check if implemented by a object that is passed to the subscriber, but which felt very wrong. Any help would be a godsend.
Here's the code I tried in playground.
#objc protocol EventConsumer {
#objc optional func sendEventAction()
#objc optional func attachEventAction()
}
class DefaultEventConsumer: EventConsumer {
}
struct CustomPublisher<Upstream: Publisher>: Publisher where Upstream.Output == String, Upstream.Failure == Never {
typealias Output = String
typealias Failure = Never
var upstreamEventPublisher: Upstream
// var eventConsumer: EventConsumer
func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, String == S.Input {
let subscription = CustomSubscription<S>(/*eventHandler: eventConsumer*/)
subscription.downstream = subscriber
subscriber.receive(subscription: subscription)
upstreamEventPublisher.sink { eventString in
subscription.sendEvent(event: eventString)
}
}
}
class CustomSubscription<Downstream: Subscriber>: Subscription where Downstream.Input == String {
var downstream: Downstream?
// var eventHandler: EventConsumer
// init(eventHandler: EventConsumer) {
// self.eventHandler = eventHandler
// }
func request(_ demand: Subscribers.Demand) {
}
func cancel() {
downstream = nil
}
func sendEvent(event: String) {
//How do I check if downstream processes the event somehow and send event if it doesnt
downstream?.receive(event)
}
}
//
//
//extension AnyPublisher where Output == String, Failure == Never {
//
// func checkAndPublishEvents(_ eventConsumer: EventConsumer) -> Self {
//
// CustomPublisher(upstreamEventPublisher: self/*, eventConsumer: eventConsumer*/)
// .eraseToAnyPublisher()
// }
//}

XCTest Single Asynchronous SetUp With Semaphores

I am working on testing an API through Alamofire. I need to make a single call to the server to prepare it for the integration test. Once that is done, I am ready to start running tests.
The usual override setUp() is run for every test, so I do not want to do that.
I have therefore chosen to override the class setUp() as described here: https://developer.apple.com/reference/xctest/xctestcase
That's all well and good, but now, I no longer can use the standard waitForExpectations. (In the class override setUp()) I get several compiler errors that tell me that I am no longer calling the same waitForExpectations because I am in a class method, not a test case.
To try to get around this, I wanted to use a semaphore like so:
class ServiceLayerTests: XCTestCase {
static var apiService: APIService = APIService()
let sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: ["http://localhost:3000/": .disableEvaluation]))
static var companyManger: UserWebResource?
static var companyManagerID = -1
override class func setUp() {
apiService.baseURL = "http://localhost:3000/"
beginIntegrationTests()
}
class func beginIntegrationTests() {
var semaphore = DispatchSemaphore(value: 0)
apiService.beginIntegrationTests(completion: {resource, error in
if let resource = resource {
if let manager = resource as? UserWebResource {
companyManger = manager
companyManagerID = manager.id
semaphore.signal()
}
}
})
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
}
}
This does not work. Under the hood, there is an alamo fire call to the server and it responds with the user to use for the integration tests. I do see the server spinning, so I know that the actual communication is happening, but I never get into the completion closure.
I suspect I am not understanding how Swift does semaphores and that I have created a deadlock somehow. If somebody has a better solution, I'd be more than happy to hear it.
I get several compiler errors that tell me that I am no longer calling
the same waitForExpectations because I am in a class method, not a
test case
That makes sense. What you probably want is to refactor so that you are in a test case:
override class func setUp() {
apiService.baseURL = "http://localhost:3000/"
}
func testIntegrationTests() {
let urlExpectation = expectation(description: "INTEGRATION TEST")
apiService.beginIntegrationTests(completion: {resource, error in
// ...
urlExpectation.fulfill()
})
// not sure what an acceptable timeout would be, I chose this at random
waitForExpectations(timeout: 25) { error in
if let error = error {
print("Error: \(error.localizedDescription)")
}
}
}
One of the best resources with some good test examples can be found here: http://nshipster.com/xctestcase/
You can create the expectation as a lazy var that executes your one-time set up and fulfills on completion.
At the beginning of your per-test setUp() function you can wait for that expectation.
None of your tests will run until it is fulfilled, and the initial setup will run only once.
class WaitForSetup_Tests: XCTestCase {
lazy var initialSetupFinished: XCTestExpectation = {
let initialSetupFinished = expectation(description: "initial setup finished")
initialSetupTask() { // your setup task that executes this closure on completion
initialSetupFinished.fulfill()
return
}
return initialSetupFinished
}()
override func setUp() {
wait(for: [initialSignOutFinished], timeout: 2.0)
// do your per-test setup here
}
}
Note: This solution avoids using the override class function setUp() class method, because I couldn't figure out how to use the expectations except for in an instance.