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

Testing a publisher on main thread seems to break test #28

Closed
si-hartmann opened this issue Sep 3, 2020 · 1 comment
Closed

Testing a publisher on main thread seems to break test #28

si-hartmann opened this issue Sep 3, 2020 · 1 comment

Comments

@si-hartmann
Copy link

We are testing a viewModel with a boolean property

@Published var isShown: Bool = false

This value is set by a publisher to which the viewModel subscribes on the Runloop.main

    service
        .observe()
        .receive(on: RunLoop.main)
        .sink(receiveCompletion: { _ in })
        { toast in
            if let toast = toast {
                self.message = toast.message
                self.isShown = true
            } else {
                self.message = ""
                self.isShown = false
            }

This causes our tests to not receive any update.

    let scheduler = TestScheduler()
    let results = scheduler.start {
        self.underTest.$isShown
    }.recordedOutput

    expect(results).to(equal(
        [
            (200, .subscription),
            (200, .input(false)),
            (300, .input(true)),
            (400, .input(false)),
        ]
    ))

If we remove .receive(on: RunLoop.main) the test succeeds.

Is there any chance to add the ability to observe the test on a different thread? Or is there a mistake in our test setup?

@tcldr
Copy link
Owner

tcldr commented Sep 3, 2020

So, EntwineTest as it stands may not be suitable for this kind of use-case.

That's because when you use receive(on:) with RunLoop.main, or any other DispatchQueue/RunLoop scheduler, Combine will schedule actions to occur on the next loop – even if it's the same as the current queue. And because EntwineTest's VirtualTime proceeds within the context of the currently executing run of the loop, when you check the .recordedOutput, they won't yet have taken place. With schedulers that schedule their actions within the context of the current loop (such as the built-in ImmediateScheduler) this isn't an issue, but if you have some asynchronous source it can be a problem.

The way EntwineTest approaches these kind of test scenarios is to encourage the de-coupling of your asynchronous source and transformations. This approach has you simulate your asynchronous source using a TestablePublisher with various run scenarios, and then testing your transformation pipeline.

This has a few benefits: 1) as the tests run in virtual time they occur within the current execution cycle meaning that your test suite isn't slowed down waiting for asynchronous tasks to complete, 2) as the virtual time scheduler schedules tasks to happen at exactly the (virtual) time you specify, you remove the category of bug introduced by race conditions when scheduling on RunLoops/DispatchQueues. (These schedulers only guarantee a best effort attempt to perform the action at around the time specified to within a particular tolerance level.) 3) you can simulate the behaviour of these kinds of race conditions, and many more complex temporal simulations without ambiguity.

There is a way to test a more integrated Publisher pipeline via the use of a blocking mechanism based on XCTestExpectation but it's not something I've implemented yet (Issue #4) – feel free to add your support for this feature there. There's also https://github.com/groue/CombineExpectations which may behave more in the way you're expecting.

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

2 participants