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

How do I expect notifications correctly in an async context? #1012

Closed
1 task done
abushnaq-work opened this issue Nov 2, 2022 · 7 comments
Closed
1 task done

How do I expect notifications correctly in an async context? #1012

abushnaq-work opened this issue Nov 2, 2022 · 7 comments

Comments

@abushnaq-work
Copy link

  • I have read CONTRIBUTING and have done my best to follow them.

What did you do?

Added code trying to test for a notification being fired in an async context:

let notification = Notification(name: Constants.BadJSONNotification)
expect {
await expect(await provisioningManager.processJSON(badJSON).to(beFalse())
}.toEventually(postNotifications(equal([notification])))

What did you expect to happen?

Initially expected it to work. I got the warning:
Instance method 'toEventually' is unavailable from asynchronous contexts; the sync version of toEventually does not work in async contexts. Use the async version with the same name as a drop-in replacement; this is an error in Swift 6

and have so far completely failed to find the async version

What actually happened instead?

I have completely failed to either find an example, documentation, or the definition of the aforementioned async version.

Environment

List the software versions you're using:

  • Quick: 6.0.0
  • Nimble: 11.1.0
  • Xcode Version: 14.0(14A309) (Open Xcode; In menubar: Xcode > About Xcode)
  • Swift Version: Xcode default (Open Xcode Preferences; Components > Toolchains. If none, use Xcode Default.)

Please also mention which package manager you used and its version. Delete the
other package managers in this list:

  • Swift Package Manager 5.6.0

Project that demonstrates the issue

Nothing to demonstrate just hopefully someone can point me to the right place if this is supported.

@abushnaq-work
Copy link
Author

Did I maybe misunderstand and this #1007 is not in 11.1 but will be in a later version?

@younata
Copy link
Member

younata commented Nov 2, 2022

My apologies for the poor wording. I struggled with how to correctly word that message.

Prepend await to the entire expression. For example: await expect(blah).toEventually(...).

@abushnaq-work
Copy link
Author

Hi and thank you for the quick answer,

It seems to be required to run on the main thread? It's tripping an assertion on Nimble/PostNotification.swift:64: Assertion failed: Only expecting closure to be evaluated on main thread..

This is my first project with async/await so I'm a bit shaky. I tried surrounding that with MainActor.run to ensure it runs on the main thread:

MainActor.run
                        {
                            let notification = Notification(name: Constants.BadJSONNotification)
                            await expect {
                                await expect(await provisioningManager.processFeatures(emptyJSON)).to(beFalse())
                            }.toEventually(postNotifications(equal([notification])))
                        }

But that triggers a compile error Cannot pass function of type '@Sendable () async -> ()' to parameter expecting synchronous function type.

Part of me wants to tag everything with await up the chain, but that doesn't make sense as I earlier had await expect(await provisioningManager.processJSON(badJSON).to(beFalse()) and it worked fine without tagging more items up the chain as async.

@younata
Copy link
Member

younata commented Nov 2, 2022

Oh. I'm sorry. I misunderstood what you were doing. This is confusing, and I'm sorry.

As of Nimble 11, toEventually does not work async Expressions. That is, expect { await someAsyncFunction() }.toEventually(...) is not supported and will not compile. In this case, Expression is the value or closure passed in to expect.

toEventually works using a polling mechanism - it runs the Expression (the value or closure passed in to expect) many times until the matcher passes. In Nimble 11, the way async Expressions are supported is a bit of a hack that I bolted on to Nimble's existing infrastructure; Nimble awaits the async Expression, then uses the resulting value with the existing, synchronous, infrastructure. This approach is fundamentally incompatible with toEventually's approach of re-running the Expression every pollInterval: all that would happen is it would test the same value it received previously.

I would like to fix this, but I've yet to come up with a good solution for all of the problems introduced by it.

Again, I'm really sorry for this confusion.

@abushnaq-work
Copy link
Author

No worries I appreciated the detailed and patient explanation. So there's currently no way to test an async function for notifications then - is that right?

Maybe I can look into XCTest for that part and have Nimble do everything else. I appreciate all the work done in Nimble - this is my second project with it and it just makes sense. :)

@younata
Copy link
Member

younata commented Nov 3, 2022

So there's currently no way to test an async function for notifications then - is that right?

That's correct. You could certainly cobble together something yourself that does it, but Nimble doesn't provide anything out of the box for this.

I created #1013 to specifically track this issue (and in general other wonkiness with the approach I took when implementing Async support for expectations).

@younata younata closed this as completed Nov 3, 2022
@abushnaq-work
Copy link
Author

Got it, thanks for your help.

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