Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RxTest didn't work with Swift concurrency #2539

Open
2 of 9 tasks
tomcheung opened this issue Aug 14, 2023 · 2 comments
Open
2 of 9 tasks

RxTest didn't work with Swift concurrency #2539

tomcheung opened this issue Aug 14, 2023 · 2 comments

Comments

@tomcheung
Copy link

Short description of the issue:

RxTest TestScheduler didn't work if the observable stream include async await usage (wrapped with AsyncThrowingStream)
In some case it will throw Fatal error: Executing on background thread. Please use MainScheduler.instance.schedule to schedule work on main thread

No matter using @MainActor or not, it won't get any value when checking the output from TestableObserver

Expected outcome:

Test case run successfully

What actually happens:

Test case got empty event

Self contained code example that reproduces the issue:

class MyClass {
    @MainActor func doubleValue(input: Observable<Int>) -> Observable<Int> { // Remove @MainActo will trigger error
        return AsyncThrowingStream {
            let val = try await input.first().value ?? 0
            return val * 2 // Use dummy value for testing here, however sometime may consume another async function in here
        }.asObservable()
    }
}

@MainActor final class RxSwiftTestingTests: XCTestCase {
    var disposeBag = DisposeBag()
    
    func testMyClass() throws {
        let scheduler = TestScheduler(initialClock: 0)
        
        let input = scheduler.createColdObservable([
            .next(0, 1),
            .completed(0)
        ]).asObservable()
        
        let c = MyClass()
        
        let observer = scheduler.createObserver(Int.self)
        c.doubleValue(input: input).bind(to: observer).disposed(by: disposeBag)
        
        scheduler.start()
        
        XCTAssertEqual(observer.events, [
            .next(0, 2),
            .completed(0)
        ])
    }
}

RxSwift/RxCocoa/RxBlocking/RxTest version/commit

RxSwift 6.6.0

Platform/Environment

  • iOS
  • macOS
  • tvOS
  • watchOS
  • playgrounds

How easy is to reproduce? (chances of successful reproduce after running the self contained code)

  • easy, 100% repro
  • sometimes, 10%-100%
  • hard, 2% - 10%
  • extremely hard, %0 - 2%

Xcode version:

  14.3.1 (14E300c)
@danielt1263
Copy link
Collaborator

danielt1263 commented Aug 14, 2023

This is expected behavior. A TestScheduler is not asynchronous. If you need to test asynchronous behavior, you will have to follow the instructions in Asynchronous Tests and Expectations instead of using TestScheduler.

This is the reason (or at least part of the reason) the async producer operators (eg timer) all accept a Scheduler as a parameter, so you can substitute a TestScheduler and turn them synchronous.

@nikolaykasyanov
Copy link
Contributor

I wonder if the introduction of custom executors in Swift 5.9 will make it possible to force concurrent Swift code to use TestScheduler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants