I'm trying to create an async unit test for the project, but cannot understand how to wait for the async subject to complete:
[Test]
public async void MicroTest()
{
var value = 2;
var first = new AsyncSubject<int>();
var second = new AsyncSubject<int>();
first.Subscribe(_ =>
{
value = _;
second.OnCompleted();
});
first.OnNext(1);
// how to wait for the second subject to complete?
Assert.AreEqual(value, 1);
}
Sync version of this test is works well:
[Test]
public void MicroTest()
{
var value = 2;
var first = new Subject<int>();
var second = new Subject<int>();
first.Subscribe(_ =>
{
value = _;
second.OnCompleted();
});
first.OnNext(1);
Assert.AreEqual(value, 1);
}
NUnit supports out of the box asynchronous methods by wrapping properly executing them in an asynchronous context and unwrapping eventual exception raised by the code itself or by failed assertions. To make sure the runner properly waits for the completion of the asynchronous tests, these cannot be async void methods.
Asynchronous code doesn't execute directly within the current flow of code. This might be because the code runs on a different thread or dispatch queue, in a delegate method, or in a callback, or because it's a Swift function marked with async . XCTest provides two approaches for testing asynchronous code.
First off, it's worth pointing out that AsyncSubject<T>
is not an asynchronous version of Subject<T>
. Both are in fact free-threaded* (see footnote).
AsyncSubject
is a specialization of Subject
intended to be used to model an operation that completes asynchronously and returns a single result. It has two noteworthy features:
It is used internally in various places, including by the ToObservable()
extension method defined on Task
and Task<T>
.
Recall AsyncSubject<T>
will only return the final result received. It does this by waiting for OnCompleted() so it knows what the final result is. Because you do not call OnCompleted()
on first
your test is flawed as the OnNext()
handler - the lambda function passed in your Subscribe call - will never be invoked.
Additionally, it is invalid not to call OnNext()
at least once on an AsyncSubject<T>
, so when you call await second;
you will get an InvalidOperationException
if you haven't done this.
If you write your test as follows, all is well:
[Test]
public async void MicroTest()
{
var value = 2;
var first = new AsyncSubject<int>();
var second = new AsyncSubject<int>();
first.Subscribe(_ =>
{
// won't be called until an OnCompleted() has
// been invoked on first
value = _;
// you must send *some* value to second
second.OnNext(_);
second.OnCompleted();
});
first.OnNext(1);
// you must do this for OnNext handler to be called
first.OnCompleted();
// how to wait for the second subject to complete
await second;
Assert.AreEqual(value, 1);
}
As a general rule I would avoid writing asynchronous tests that could wait forever. This gets particularly annoying when it causes resource drains on build servers. Use some kind of timeout e.g:
await second.Timeout(TimeSpan.FromSeconds(1));
No need to handle the exception since that is enough for the test to fail.
**I've borrowed this term from the COM lexicon. In this sense I mean that they, as with most of the Rx framework components, will generally run on whatever thread you happen to invoke their methods on. Being free-threaded doesn't necessarily mean being fully thread safe though. In particular, unlike AsyncSubject<T>
, Subject<T>
doesn't protect you from the Rx grammar violation of making overlapping calls to OnNext
. Use Subject.Synchronize
or Observable.Synchronize
for this protection.*
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With