📜 ⬆️ ⬇️

Asynchronous programming - event testing

Sometimes you have to write tests for events, and it is inconvenient to do that — additional methods and fields start to multiply very quickly. How to test events in C # and I want to tell.


To begin with an example. I have an API that asynchronously downloads a web page. To get the page, I subscribe to the event [ 1 ] and thereby create another method in which my asserts actually live. Naturally, the test method itself has to be blocked because otherwise, the test runner will simply exit without waiting for my callback.

[TestFixture]<br/>
public class MyTests<br/>
{<br/>
private ManualResetEvent waitHandle;<br/>
[Test] <br/>
public void TestAsyncPageDownloading()<br/>
{<br/>
waitHandle = new ManualResetEvent( false );<br/>
⋮<br/>
wdp.GetWebDataCompleted += wdp_GetWebDataCompleted;<br/>
wdp.GetWebDataAsync( new Uri( "http://nesteruk.org/blog" ), new object ());<br/>
waitHandle.WaitOne(); // assert'
}<br/>
void wdp_GetWebDataCompleted( object sender, GetWebDataCompletedEventArgs e)<br/>
{<br/>
StreamReader sr = new StreamReader(e.Stream);<br/>
string s = sr.ReadToEnd();<br/>
// test runner'
Assert.Contains(s, "Dmitri" , "My webpage should have my name." );<br/>
waitHandle.Set(); //
}<br/>
}<br/>

Unfortunately, this approach does not take into account the situation in which the test “freezes” and thus slows down the testing process. To do this, we can add a timeout for the WaitOne() call, but then we must also test the probability that the callback will not be called at all. To do this, we have to add another variable of type bool . Here's what happens:
')
private bool callbackInvoked;<br/>
⋮<br/>
[Test]<br/>
public void TestAsyncPageDownloading()<br/>
{<br/>
waitHandle = new ManualResetEvent( false );<br/>
wdp.GetWebDataCompleted += wdp_GetWebDataCompleted;<br/>
wdp.GetWebDataAsync( new Uri( "http://nesteruk.org/blog" ), new object ());<br/>
waitHandle.WaitOne( 5000 ); // 5 assert'
Assert.IsTrue(callbackInvoked, "Callback method was never called" ); <br/>
}<br/>
void wdp_GetWebDataCompleted( object sender, GetWebDataCompletedEventArgs e)<br/>
{<br/>
callbackInvoked = true ; //
StreamReader sr = new StreamReader(e.Stream);<br/>
string s = sr.ReadToEnd();<br/>
// test runner'
Assert.Contains(s, "Dmitri" , "My webpage should have my name." );<br/>
waitHandle.Set(); //
}<br/>

In order to avoid multiplying the number of methods and variables in our test fixture, it would be useful to put the “asynchronous tester” into a separate class that could be used as a manager. [ 2 ]

public class EventTester<SenderType, ArgumentType> : IDisposable<br/>
{<br/>
private readonly ManualResetEvent waitHandle;<br/>
private readonly Action<SenderType, ArgumentType> postHocTests;<br/>
private bool called;<br/>
private IAsyncResult waitToken;<br/>
public EventTester(Action<SenderType, ArgumentType> postHocTests)<br/>
{<br/>
waitHandle = new ManualResetEvent( false );<br/>
this .postHocTests = postHocTests;<br/>
}<br/>
public void Handler(SenderType sender, ArgumentType args)<br/>
{<br/>
waitHandle.Set();<br/>
waitToken = postHocTests.BeginInvoke(sender, args, null , null );<br/>
}<br/>
public void Wait( int mullisecondsTimeout)<br/>
{<br/>
called = waitHandle.WaitOne(mullisecondsTimeout);<br/>
}<br/>
public void Dispose()<br/>
{<br/>
Assert.IsTrue(called, "The event was never handled" );<br/>
postHocTests.EndInvoke(waitToken);<br/>
}<br/>
}<br/>

What does this tester do? First, it caches the callback method that needs to be called for post hoc tests. Further, it provides the Wait() method, which is delegated to our ManualResetEvent and thus allows us to wait a while until an event handler is called. As soon as it is called - we immediately run the final tests. And then - a small trick - we implement IDisposable() and in the Dispose() method we check if the handler was called. Since assert interrupts execution, the subsequent EndInvoke() will be called only when it is relevant.

As for the test itself, now it looks like this:

[Test]<br/>
public void BetterAsyncTest()<br/>
{<br/>
using ( var eventTester = new EventTester< object , GetWebDataCompletedEventArgs>(<br/>
(o, args) =><br/>
{<br/>
StreamReader sr = new StreamReader(args.Stream);<br/>
string s = sr.ReadToEnd();<br/>
Assert.Contains(s, "Dmitri" , "My webpage should have my name." );<br/>
}))<br/>
{<br/>
wdp.GetWebDataCompleted += eventTester.Handler;<br/>
wdp.GetWebDataAsync( new Uri( "http://nesteruk.org/blog" ), new object ());<br/>
eventTester.Wait(5000);<br/>
}<br/>
}<br/>

This is certainly not the most readable test in the world, but at least it does not produce additional fields and methods in the class body. Testing events as well as testing pairs of BeginXxx()/EndXxx() is a simple task, since it always predictably includes two elements - the call and the handler, the beginning and the end. ■

Notes
  1. In this case, I'm testing the MicrosoftSubscription.Sync.WebDataProvider class is part of the Syndicated Client Experiences SDK , a framework that uses the well-known photoSuru application.
  2. This approach was suggested by some vansickle in the comments on my blog page .

Source: https://habr.com/ru/post/71410/


All Articles