Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test code involving SynchronizationContext?

I have following code that I'm trying to test using NUnit and Rhino mocks.

void _tracker_IndexChanged(object sender, IndexTrackerChangedEventArgs e)
{
    //  _context is initialised as 
    //  SynchronizationContext _context = SynchronizationContext.Current;
    // _tracker.Index is stubbed to return the value 100
    _context.Post(o => _view.SetTrackbarValue(_tracker.Index), null);
}

In testcase I have set the expectation as

_view.Expect(v => v.SetTrackbarValue(100));

and when I verify expectation, the unit test fails randomaly with message

Test(s) failed. Rhino.Mocks.Exceptions.ExpectationViolationException :
IIndexTrackerView.SetTrackbarValue(100); Expected #1, Actual #0.

I failed identify the problem here, How do I fix it?

like image 313
Prashant Cholachagudda Avatar asked Feb 23 '23 05:02

Prashant Cholachagudda


2 Answers

I usually solve problems like this by encapsulating global state with an abstract class or interface that can be instanced and mocked out. Then instead of directly accessing the global state, I inject an instance of my abstract class or interface into the code that uses it.

This lets me mock out the global behavior, and makes it so my tests don't depend on or exercise that unrelated behavior.

Here's one way you could do that.

public interface IContext
{
    void Post(SendOrPostCallback d, Object state);
}

public class SynchronizationContextAdapter : IContext
{
    private SynchronizationContext _context;

    public SynchronizationContextAdapter(SynchronizationContext context)
    {
        _context = context;
    }

    public virtual void Post(SendOrPostCallback d, Object state)
    {
        _context.Post(d, state);
    }
}

public class SomeClass
{
    public SomeClass(IContext context)
    {
        _context = context;
    }

    void _tracker_IndexChanged(object sender, IndexTrackerChangedEventArgs e)
    {
        _context.Post(o => _view.SetTrackbarValue(_tracker.Index), null);
    }
    // ...
}

Then you can mock or stub out IContext so you don't have to worry about threading, and can use a simple implementation that just executes the delegate.

If I wrote unit tests to mock this out, I'd also write higher level "integration" tests that didn't mock it out, but had less granular verifications.

like image 69
Merlyn Morgan-Graham Avatar answered Mar 08 '23 21:03

Merlyn Morgan-Graham


I haven't used Rhino Mocks in a while so I can't remember the exact syntax but if refactoring things as Merlyn suggests is not an option then another solution would be to use a ManualResetEvent to wait until your mock has been acted on.

Something like this:

[Test]
public void ATest(){
  ManualResetEvent completed = new ManualResetEvent(false);

  _view.Expect(v => v.SetTrackbarValue(100)).Do(() => completed.Set());
  //Stuff done here...   
  Assert.IsTrue(completed.WaitOne(1000), "Waited a second for a call that never arrived!");

}

That way you can wait until the other thread triggers the event, at which point you can continue. Make sure there is a sensible timeout so you dont wait forever though!

like image 31
Russell Troywest Avatar answered Mar 08 '23 20:03

Russell Troywest