Mocking a method that uses an asynchronous callback with moq - c#

I'm unit testing some asynchronous code. I have tried to abstract it to make the issue more clear. My issue is that I want to set up the mocked Bar to execute Foo's private callback method after BeginWork returns. The callback is supposed to call Set() on the ManualResetEvent allowing the calling thread to continue to run. When I run the test my thread blocks indefinitely at the call to WaitOne().
Code under test:
using System.Threading;
using NUnit.Framework;
using Moq;
using System.Reflection;
public interface IBar
{
int BeginWork(AsyncCallback callback);
}
public class Bar : IBar
{
public int BeginWork(AsyncCallback callback)
{
// do stuff
} // execute callback
}
public class Foo
{
public static ManualResetEvent workDone = new ManualResetEvent(false);
private IBar bar;
public Foo(IBar bar)
{
this.bar = bar;
}
public bool DoWork()
{
bar.BeginWork(new AsyncCallback(DoWorkCallback));
workDone.WaitOne(); // thread blocks here
return true;
}
private void DoWorkCallback(int valueFromBeginWork)
{
workDone.Set();
}
}
Test Code:
[Test]
public void Test()
{
Mock<IBar> mockBar = new Mock<IBar>(MockBehavior.Strict);
// get private callback
MethodInfo callback = typeof(Foo).GetMethod("DoWorkCallback",
BindingFlags.Instance | BindingFlags.NonPublic);
mockBar.Setup(() => BeginWork(It.IsAny<AsyncCallback>()))
.Returns(0).Callback(() => callback.Invoke(0));
Foo = new Foo(mockBar.Object);
Assert.That(Foo.DoWork());
}

First observation was that you pass in a mocked ISocket in state and try to cast it to Socket in async callback which will result in a null error which means connectDone.Set() is never called so WaitOne will not unblock.
Change that to
private void ConnectCallback(IAsyncResult result) {
ISocket client = (ISocket)result.AsyncState;
client.EndConnect(result);
connectDone.Set();
}
Second observation was that you were not setting up the mocked calls correctly. No need for reflection here as you needed to get the passed arguments from the mock and invoke then in the mock callback setup
The following is based on your original code. Review it to get an understanding of what was explained above.
[TestClass]
public class SocketManagerTests {
[TestMethod]
public void ConnectTest() {
//Arrange
var mockSocket = new Mock<ISocket>();
//async result needed for callback
IAsyncResult mockedIAsyncResult = Mock.Of<IAsyncResult>();
//set mock
mockSocket.Setup(_ => _.BeginConnect(
It.IsAny<EndPoint>(), It.IsAny<AsyncCallback>(), It.IsAny<object>())
)
.Returns(mockedIAsyncResult)
.Callback((EndPoint ep, AsyncCallback cb, object state) => {
var m = Mock.Get(mockedIAsyncResult);
//setup state object on mocked async result
m.Setup(_ => _.AsyncState).Returns(state);
//invoke provided async callback delegate
cb(mockedIAsyncResult);
});
var manager = new SocketManager(mockSocket.Object);
//Act
var actual = manager.Connect();
//Assert
Assert.IsTrue(actual);
mockSocket.Verify(_ => _.EndConnect(mockedIAsyncResult), Times.Once);
}
}
Finally I believe you should consider changing this code to use TPL to get around the whole call back and IAsyncResult drama. Basically exposing an async API and wrapping the calls with a TaskCompletionSource<T> but I guess that is outside of the scope of this question.

Related

Is this usage of Task.Run() bad practice?

If the use of Task.Run in this case justifiable ?
Currently I run this code in a WinForms app, but later on it will be used in a ASP.NET project as a HostedService/BackgroundService. I am not sure if this is comparable then.
After reading multiple blogs about async/await and Tasks I feel like the Task.Run(() => .. should be implemented in the calling method Manager.SyncLoop(). But what if the implementation of IConnection is truely asynchronous, wouldn't that be code smell ?
private async void button1_Click(object sender, EventArgs e)
{
// this should be handled by the BackgroudService, WinForms is used just for testing
var m = new Manager();
m.Connection = new ConnectionA();
m.ExecuteAsync();
}
}
public interface IConnection
{
Task<object> ReadAsync();
}
// assume that i cannot change this
public class SomeLib
{
private Random random = new Random();
public object SyncReading()
{
Thread.Sleep(5000);
return random.Next(); ;
}
}
public class ConnectionA : IConnection
{
private SomeLib lib = new SomeLib();
public Task<object> ReadAsync()
{
// is this usage of Task.Run ok?
var v = Task.Run(() => lib.SyncReading());
return v;
}
// this will block UI
//public Task<object> ReadAsync()
//{
// return Task.FromResult(lib.SyncReading());
//}
}
public class Manager
{
public IConnection Connection { get; set; }
public async Task ExecuteAsync()
{
await SyncLoop();
}
public async Task SyncLoop()
{
while (true)
{
var i = await Connection.ReadAsync();
await Task.Delay(2000);
}
}
}
First, can you change IConnection? Is this synchronous implementation the primary one, or is it just one of many?
If you can change IConnection, then make it synchronous, and you can use Task.Run in the implementation of ExecuteAsync.
If IConnection needs to remain asynchronous, then I would say to implement ConnectionA.ReadAsync synchronously. Then have the Task.Run in ExecuteAsync as normal. The key behind this technique is that an asynchronous (Task-returning) signature means that the implementation may be asynchronous, not that it must be asynchronous.

How can I determine if a function wrapped in an if statement gets called?

I have a class, like this one:
public class SomeClass: ISomeClass
{
public async Task<ServiceResponse> PerformAction(Context context)
{
if(IsPartner(context))
{
SendTextMessageAsync(context);
}
}
public bool IsPartner(Context context)
{
return SomeStaticHelperClass.GetIsPartner(context.PartnerId);
}
public async void SendTextMessageAsync(Context context)
{
// sends the message
}
}
ISomeClass exposes all three methods.
I'm using Moq and I would like to write a test that verifies
when IsPartner returns true
then SendTextMessage is called once.
I've tried everything I can think of.
This is my latest attempt:
public class ReceiptServiceTests
{
[Fact]
public async void SendReceiptsProofsNeededNudgeAsync_ShouldCallSendTextMessageAsync_WhenSessionPartnerIdIsNotNullOrEmptyString()
{
var context = new Context { PartnerId = "12345" };
var mockSomeClass = new Mock<ISomeClass>();
// force IsPartner to return true
mockSomeClass.Setup(m => m.IsPartner(context)).Returns(true);
// call PerformAction which should call SendTextMessageAsync
// because IsPartner will return true
await mockSomeClass.Object.PerformAction(context);
// verify SendTextMessageAsync was actually called
mockSomeClass.Verify(m => m.SendTextMessageAsync(It.IsAny<Context>()), Times.AtLeastOnce);
}
}
I keep getting following error:
(Expected invocation on the mock at least once, but was never performed: m => m.SendTextMessageAsync)
which i'm guessing is because SendTextMessageAsync is never called. I'm stuck here though, I don't understand how to properly call it.
I know some (or a lot) of this code doesn't make sense, I've changed it for brevity.
What I'm really just trying to figure out is how can I determine if a function (SendTextMessageAsync) is called when it is wrapped in an if statement that calls another function that returns true.
As rgvlee as already pointed out you can only mock dependencies and verify their method calls.
In your case SomeStaticHelperClass is your dependency and its GetIsPartner method that could be verified.
Unfortunately static classes are not good candidates for mocking. If you could replace that static class reference to an interface then you would be able to mock that easily.
SomeClass uses IPartnerService
public class SomeClass: ISomeClass
{
private readonly IPartnerService partnerSvc;
public SomeClass(IPartnerService partnerSvc)
{
this.partnerSvc = partnerSvc;
}
public async Task<ServiceResponse> PerformAction(Context context)
{
if(IsPartner(context))
{
SendTextMessageAsync(context);
}
}
public bool IsPartner(Context context)
{
return partnerSvc.GetIsPartner(context.PartnerId);
}
public async void SendTextMessageAsync(Context context)
{
// sends the message
}
}
SomeClass is using a IPartnerService mock during unit testing
public class ReceiptServiceTests
{
[Fact]
public async Task GivenAPartner_WhenICallPerformAction_ThenItCallsPartnerService()
{
//Arrange
const string partnerId = "12345";
var context = new Context { PartnerId = partnerId };
var partnerSvcMock = new Mock<IPartnerService>();
partnerSvcMock
.Setup(svc => svc.GetIsPartner(partnerId))
.Returns(true);
var SUT = new SomeClass(partnerSvcMock);
//Act
_ = await SUT.PerformAction(context);
//Assert
partnerSvcMock.Verify(svc => svc.GetIsPartner(partnerId), Times.Once);
}
}

Match a func delegate in argument matcher in nsubstitute Received method

I am trying to check if a method is called a specific number of times on a mock instance of a class. The problem is the method has a func delegate and that is not matching.
I have the following scenario:
public interface ISomeService: IService
{
Task CleanupMethod(CancellationToken cancellationToken);
}
public interface I
{
Task invokedMethod(string aName, Func<IService, Task> action);
}
public class ClassGoingToBeUnitTested
{
// instance of I
private I instanceOfI;
// a list of names.
private static readonly string[] serviceNames =
{
"Name1",
"Name2"
};
// constructor
public ClassGoingToBeUnitTested(I passedInstance)
{
this.instanceOfI = passedInstance;
}
public void methodToBeUnitTested(object cancellationToken)
{
// my logic here
// here I am calling invokedMethod method to known number of times.
// something like this.
try
{
IEnumerable<Task> someTasks = serviceNames.Select(
name => this.instanceOfI.invokedMethod(
name,
service => ((ISomeService)service).CleanupMethod((CancellationToken)cancellationToken)
));
// here I run the tasks
Task.WaitAll(someTasks.ToArray());
}
catch
{
// proper catching of exceptions
}
// other logic
}
}
[TestClass]
public class ClassGoingToBeUnitTestedTest
{
// mock of I interface
private I IMock;
// ClassGoingToBeUnitTested object
ClassGoingToBeUnitTested classGoingToBeUnitTested;
[TestInitialize]
public void init()
{
this.IMock = Substitute.For<I>();
this.classGoingToBeUnitTested = new ClassGoingToBeUnitTested(this.IMock);
}
[TestMethod]
public void methodToBeUnitTested_Success()
{
// Arrange
var cancellationTokenSource = new CancellationTokenSource();
// Act
this.classGoingToBeUnitTested.methodToBeUnitTested(cancellationTokenSource.Token);
// Assert
// this is throwing exception.
this.IMock.Received(1).invokedMethod(
"Name1",
service => ((ISomeService)service).CleanupMethod((CancellationToken)cancellationTokenSource.Token)); // problem lies in this line.
}
}
In the above code if I change ((ISomeService)service).CleanupMethod((CancellationToken)cancellationTokenSource.Token)) to Arg.Any<Func<IService, Task>(), it runs perfectly. But I don't want to check that for my use case.
Till now, I have been able to debug that the argument matcher is matching the delegates by reference and hence is not able to match the arguments correctly. But I am not able to correctly match the arguments.
I also tried to invoke the delegate but I did not succeed. I think I am missing something. Any help would be highly appreciated.
I solved this by using Invoke. I first mocked the behaviour of invokedMethod to invoke a mocked serviceInstanceMock whenever it is called and then checked the number of times CleanupMethod is called on the serviceInstanceMock itself.
[TestClass]
public class ClassGoingToBeUnitTestedTest
{
// mock of I interface
private I IMock;
// mock of ISomeService
private ISomeService someServiceMockInstance;
// ClassGoingToBeUnitTested object
ClassGoingToBeUnitTested classGoingToBeUnitTested;
[TestInitialize]
public void init()
{
this.IMock = Substitute.For<I>();
this.ISomeService = Substitute.For<ISomeService>();
this.classGoingToBeUnitTested = new ClassGoingToBeUnitTested(this.IMock);
}
[TestMethod]
public void methodToBeUnitTested_Success()
{
// Arrange
var cancellationTokenSource = new CancellationTokenSource();
this.IMock.invokedMethod(
Arg.Any<string>,
Arg.Do<Func<IService, Task>(x => x.Invoke(this.someServiceMockInstance)));
// Act
this.classGoingToBeUnitTested.methodToBeUnitTested(cancellationTokenSource.Token);
// Assert
this.IMock.Received(1).invokedMethod(
"Name1",
Arg.Any<Func<IService, Task>()); // changed this
this.IMock.Received(1).invokedMethod(
"Name2",
Arg.Any<Func<IService, Task>()); // added this as well
// adding to check the Received call on the service instance
this.someServiceMockInstance.Received(2).CleanupMethod(cancellationTokenSource.Token);
}
}

MVVM + WCF Async callbacks

I have a WCF service (IMyService) that I wrap into a service (ICentralService), so that I have one central service that I can inject in my ViewModels. This would give me the advantage of changing/adding things at one location just before the WCF service is called.
Now because I need to make an async wcf call, my viewmodel also needs to be async. I'm have a callback from my viewmodel, but my CentralService also has its own callback to call the End... method.
Question: what is the best way to pass my viewmodel-callback to the EndTest method in the central service, so that this EndTest method could notify the callback on the viewmodel?
Or maybe there is a better way?
I could directly inject IMyService in my ViewModel but then I don't have a central location (CentralService) where I can manipulate/inject data before sending it to the server via WCF.
Note: I'm on .NET 4.0, can't use "await", I'm also using WCF IAsyncResult Model (server-side).
Code:
[ServiceContract(....)]
public interface IMyService {
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginTest(int x, AsyncCallback, object state);
int EndTest(IAsyncResult result);
}
public interface ICentralService {
void WorkItAsync(int x, AsyncCallback callback);
}
public class CentralService : ICentralService
{
private IMyService _Srv;
public CentralService(IMyService srv)
{
_Srv = srv;
}
public void WorkItAsync(int x, AsyncCallback callback)
{
// callback is the callback from my viewmodel
_Srv.BeginTest(x, new AsyncCallback(WorkItCompleted));
}
private void WorkItCompleted(IAsyncResult r)
{
// ...
int result = _Srv.EndTest(r);
// Need to call callback from viewmodel now to notify it is ready.
}
}
public class SomeViewModel : INotifyPropertyChanged
{
private ICentralService _Central;
public SomeViewModel(ICentralService central) {
_Central = central;
}
private void A() {
_Central.WorkItAsync(5, new AsyncCallback(B));
}
private void B(object test) {
// do something with the result
}
}
UPDATE:
I've managed to wrap my IMyService into my ICentralService and pass the result from WCF (IMyService) to my viewmodel via ICentralService.
First attempt/idea, but this did not return my "dataResult" value to my viewmodel:
public void WorkItAsync(int x, AsyncCallback callback)
{
var task = Task<int>.Factory.StartNew(() =>
{
int dataResult = -1;
_Srv.BeginTest(x, (ar) => {
dataResult = _Srv.EndTest(ar);
}, null);
return dataResult ;
});
if (callback != null)
task.ContinueWith((t) => callback(t));
return task;
}
Second attempt (works):
public void WorkItAsync(int x, AsyncCallback callback)
{
TaskCompletionSource<int> tcs1 = new TaskCompletionSource<int>();
Task<int> t1 = tcs1.Task;
Task<int>.Factory.StartNew(() =>
{
int dataResult = -1;
_Srv.BeginTest(x, (ar) => {
dataResult = _Srv.EndTest(ar);
tcs1.SetResult(dataResult);
}, null);
return dataResult;
});
if (callback != null)
t1.ContinueWith((t) => callback(t));
return t1;
}
I'm not sure if this is a good solution using the TaskCompletionSource, but for now it seems to works. (Too bad I have to return a useless -1 dataResult value).
The second update looks pretty good, but I think you can use TaskFactory.FromAsync to avoid the TaskCompletionSource for now. Here is a reference page for FromAsync with examples. I haven't tested this, but the method may look something like this:
public interface ICentralService
{
// Just use .ContinueWith to call a completion method
Task<int> WorkItAsync(int x);
}
public class CentralService : ICentralService
{
private IMyService _Srv;
public CentralService(IMyService srv)
{
_Srv = srv;
}
public Task<int> WorkItAsync(int x)
{
// Callback is handled in ViewModel using ContinueWith
return Task<int>.Factory.FromAsync(_Src.BeginTest, _Src.EndTest, x);
}
}
public class SomeViewModel : INotifyPropertyChanged
{
private ICentralService _Central;
public SomeViewModel(ICentralService central)
{
_Central = central;
}
private void A()
{
_Central.WorkItAsync(5)
.ContinueWith(prevTask =>
{
// Handle or throw exception - change as you see necessary
if (prevTask.Exception != null)
throw prevTask.Exception;
// Do something with the result, call another method, or return it...
return prevTask.Result;
});
}
}
You could use lambdas:
public void WorkItAsync(int x, AsyncCallback callback)
{
// callback is the callback from my viewmodel
_Srv.BeginTest(x, ar=>{
int result = _Srv.EndTest(ar);
callback(ar);
});
}
ar will be your IAsyncResult if needed. You could even run multiple requests in parallel, because the callback variable will be in local scope for each of the parallel calls.

How to Unit Test BackgroundWorker + PRISM InteractionRequest?

I have a reoccurring pattern in my WPF MVVM applications that has the following structure.
public class MyViewModel : NotificationObject
{
private readonly IService _DoSomethingService;
private bool _IsBusy;
public bool IsBusy
{
get { return _IsBusy; }
set
{
if (_IsBusy != value)
(
_IsBusy = value;
RaisePropertyChanged(() => IsBusy);
)
}
}
public ICommand DisplayInputDialogCommand { get; private set; }
public InteractionRequest<Notification> Error_InteractionRequest { get; private set; }
public InteractionRequest<Confirmation> GetInput_InteractionRequest { get; private set; }
// ctor
public MyViewModel(IService service)
{
_DoSomethingService = service;
DisplayInputDialogCommand = new DelegateCommand(DisplayInputDialog);
Error_InteractionRequest = new InteractionRequest<Notification>();
Input_InteractionRequest = new InteractionRequest<Confirmation>();
}
private void DisplayInputDialog()
{
Input_InteractionRequest.Raise(
new Confirmation() {
Title = "Please provide input...",
Content = new InputViewModel()
},
ProcessInput
);
}
private void ProcessInput(Confirmation context)
{
if (context.Confirmed)
{
IsBusy = true;
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += new DoWorkEventHandler(DoSomethingWorker_DoWork);
bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(DoSomethingWorker_RunWorkerCompleted);
bg.RunWorkerAsync();
}
}
private void DoSomethingWorker_DoWork(object sender, DoWorkEventArgs e)
{
_DoSomethingService.DoSomething();
}
private void DoSomethingWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
IsBusy = false;
if (e.Error != null)
{
Error_InteractionRequest.Raise(
new Confirmation() {
Title = "Error",
Content = e.Error.Message
}
);
}
}
}
Essentially, the pattern describes a dialog oriented workflow that allows the user to initiate (and provide input to) a long running operation without locking the UI. A concrete example of this pattern might be a "Save As..." operation where the user clicks a "Save As..." button, then keys in a text value for filename in a popup dialog, then clicks the dialog OK button, then watches a spin animation while their data is saved under the specified filename.
In the provided code example, initiating this workflow will perform the following operations.
Raise the Input_InteractionRequest Raised event to display a dialog in the UI for the purpose of collecting user input.
Invoke the ProcessInput callback (triggered when the user completes the dialog).
Check the Confirmed property of the InteractionRequest context to determine if the dialog was confirmed or canceled.
If confirmed...
Set the IsBusy flag.
Start a BackgroundWorker to perform the long running _DoSomethingService.DoSomething() operation.
Unset the IsBusy flag.
If an error occurred in DoSomething_DoWork, raise the Error_InteractionRequest Raised event to display a message box in the UI for the purpose of informing the user that the operation was not successful.
I would like to maximize unit testing coverage for this pattern, but I'm not quite sure how to approach it. I would like to avoid unit testing non-public members directly since the specific implementation of this pattern could change over time and in fact varies from instance to instance throughout my applications. I have considered the following options, but none of them seem appropriate.
Replace BackgroundWorker with IBackgroundWorker and inject it via ctor. Use a synchronous IBackgroundWorker during tests to ensure that unit tests do not complete before DoWork/RunWorkerCompleted methods are called. This would require a lot of refactoring and does not address testing the InteractionRequest callback either.
Use System.Threading.Thread.Sleep(int) to allow the BackgroundWorker operation to complete before the assertion stage. I don't like this because it is slow and I still don't know how to test code paths in the InteractionRequest callback.
Refactor the BackgroundWorker methods and InteractionRequest callback into Humble Objects that can be syncronously and independently tested. This seems promising, but structuring it has me stumped.
Unit test DoSomethingWorker_DoWork, DoSomethingWorker_RunWorkerCompleted, and ProcessInput synchronously and independently. This would give me the coverage I need, but I would be testing against a specific implementation rather than the public interface.
What is the best way to unit test and/or refactor the above pattern to provide maximum code coverage?
EDIT: See update below for simpler alternative (.NET 4.0+ only).
This pattern can be easily tested by abstracting the mechanics of BackgroundWorker behind an interface and then testing against that interface as described in this question. Once the quirks of the BackgroundWorker have been obscured behind an interface, testing the InteractionRequest becomes straightforward.
This is the interface I decided to use.
public interface IDelegateWorker
{
void Start<TInput, TResult>(Func<TInput, TResult> onStart, Action<TResult> onComplete, TInput parm);
}
This interface exposes a single Start method that accepts the following parameters.
Func<TInput, TResult> onStart - Comparable to BackgroundWorker.DoWork. This is where you would perform the primary work of your background operation. This delegate should accept a single parameter of type TInput and return a value of type TResult which should be passed on to the onComplete delegate.
Action<TResult> onComplete - Comparable to BackgroundWorker.RunWorkerCompleted. This delegate will be invoked after the onStart delegate completes. This is where you would perform any post-processing work. This delegate should accept a single parameter of type TResult.
TInput parm - The initial value to pass into the onStart delegate (or null if the onStart delegate does not require input). Comparable to passing an argument value to the Backgroundworker.RunWorkerAsync(object argument) method.
You can then use dependency injection to replace the BackgroundWorker instance with an instance of IDelegateWorker. For example, the rewritten MyViewModel now looks like this.
public class MyViewModel : NotificationObject
{
// Dependencies
private readonly IService _doSomethingService;
private readonly IDelegateWorker _delegateWorker; // new
private bool _IsBusy;
public bool IsBusy
{
get { return _IsBusy; }
set
{
if (_IsBusy != value)
{
_IsBusy = value;
RaisePropertyChanged(() => IsBusy);
}
}
}
public ICommand DisplayInputDialogCommand { get; private set; }
public InteractionRequest<Notification> ErrorDialogInteractionRequest { get; private set; }
public InteractionRequest<Confirmation> InputDialogInteractionRequest { get; private set; }
// ctor
public MyViewModel(IService service, IDelegateWorker delegateWorker /* new */)
{
_doSomethingService = service;
_delegateWorker = delegateWorker; // new
DisplayInputDialogCommand = new DelegateCommand(DisplayInputDialog);
ErrorDialogInteractionRequest = new InteractionRequest<Notification>();
InputDialogInteractionRequest = new InteractionRequest<Confirmation>();
}
private void DisplayInputDialog()
{
InputDialogInteractionRequest.Raise(
new Confirmation()
{
Title = "Please provide input...",
Content = new DialogContentViewModel()
},
ProcessInput
);
}
private void ProcessInput(Confirmation context)
{
if (context.Confirmed)
{
IsBusy = true;
// New - BackgroundWorker now abstracted behind IDelegateWorker interface.
_delegateWorker.Start<object, TaskResult<object>>(
ProcessInput_onStart,
ProcessInput_onComplete,
null
);
}
}
private TaskResult<object> ProcessInput_onStart(object parm)
{
TaskResult<object> result = new TaskResult<object>();
try
{
result.Result = _doSomethingService.DoSomething();
}
catch (Exception ex)
{
result.Error = ex;
}
return result;
}
private void ProcessInput_onComplete(TaskResult<object> tr)
{
IsBusy = false;
if (tr.Error != null)
{
ErrorDialogInteractionRequest.Raise(
new Confirmation()
{
Title = "Error",
Content = tr.Error.Message
}
);
}
}
// Helper Class
public class TaskResult<T>
{
public Exception Error;
public T Result;
}
}
This technique allows you to avoid the quirks of the BackgroundWorker class by injecting a syncronous (or mock) implementation of IDelegateWorker into MyViewModel when testing and an asyncronous implementation for production. For example, you could use this implementation when testing.
public class DelegateWorker : IDelegateWorker
{
public void Start<TInput, TResult>(Func<TInput, TResult> onStart, Action<TResult> onComplete, TInput parm)
{
TResult result = default(TResult);
if (onStart != null)
result = onStart(parm);
if (onComplete != null)
onComplete(result);
}
}
And you could use this implementation for production.
public class ASyncDelegateWorker : IDelegateWorker
{
public void Start<TInput, TResult>(Func<TInput, TResult> onStart, Action<TResult> onComplete, TInput parm)
{
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += (s, e) =>
{
if (onStart != null)
e.Result = onStart((TInput)e.Argument);
};
bg.RunWorkerCompleted += (s, e) =>
{
if (onComplete != null)
onComplete((TResult)e.Result);
};
bg.RunWorkerAsync(parm);
}
}
With this infrastructure in place, you should be able to test all aspects of your InteractionRequest as follows. Note that I am using MSTest and Moq and have achieved 100% coverage according to the Visual Studio Code Coverage tool although that number is somewhat suspect to me.
[TestClass()]
public class MyViewModelTest
{
[TestMethod()]
public void DisplayInputDialogCommand_OnExecute_ShowsDialog()
{
// Arrange
Mock<IService> mockService = new Mock<IService>();
Mock<IDelegateWorker> mockWorker = new Mock<IDelegateWorker>();
MyViewModel vm = new MyViewModel(mockService.Object, mockWorker.Object);
InteractionRequestTestHelper<Confirmation> irHelper
= new InteractionRequestTestHelper<Confirmation>(vm.InputDialogInteractionRequest);
// Act
vm.DisplayInputDialogCommand.Execute(null);
// Assert
Assert.IsTrue(irHelper.RequestRaised);
}
[TestMethod()]
public void DisplayInputDialogCommand_OnExecute_DialogHasCorrectTitle()
{
// Arrange
const string INPUT_DIALOG_TITLE = "Please provide input...";
Mock<IService> mockService = new Mock<IService>();
Mock<IDelegateWorker> mockWorker = new Mock<IDelegateWorker>();
MyViewModel vm = new MyViewModel(mockService.Object, mockWorker.Object);
InteractionRequestTestHelper<Confirmation> irHelper
= new InteractionRequestTestHelper<Confirmation>(vm.InputDialogInteractionRequest);
// Act
vm.DisplayInputDialogCommand.Execute(null);
// Assert
Assert.AreEqual(irHelper.Title, INPUT_DIALOG_TITLE);
}
[TestMethod()]
public void DisplayInputDialogCommand_OnExecute_SetsIsBusyWhenDialogConfirmed()
{
// Arrange
Mock<IService> mockService = new Mock<IService>();
Mock<IDelegateWorker> mockWorker = new Mock<IDelegateWorker>();
MyViewModel vm = new MyViewModel(mockService.Object, mockWorker.Object);
vm.InputDialogInteractionRequest.Raised += (s, e) =>
{
Confirmation context = e.Context as Confirmation;
context.Confirmed = true;
e.Callback();
};
// Act
vm.DisplayInputDialogCommand.Execute(null);
// Assert
Assert.IsTrue(vm.IsBusy);
}
[TestMethod()]
public void DisplayInputDialogCommand_OnExecute_CallsDoSomethingWhenDialogConfirmed()
{
// Arrange
Mock<IService> mockService = new Mock<IService>();
IDelegateWorker worker = new DelegateWorker();
MyViewModel vm = new MyViewModel(mockService.Object, worker);
vm.InputDialogInteractionRequest.Raised += (s, e) =>
{
Confirmation context = e.Context as Confirmation;
context.Confirmed = true;
e.Callback();
};
// Act
vm.DisplayInputDialogCommand.Execute(null);
// Assert
mockService.Verify(s => s.DoSomething(), Times.Once());
}
[TestMethod()]
public void DisplayInputDialogCommand_OnExecute_ClearsIsBusyWhenDone()
{
// Arrange
Mock<IService> mockService = new Mock<IService>();
IDelegateWorker worker = new DelegateWorker();
MyViewModel vm = new MyViewModel(mockService.Object, worker);
vm.InputDialogInteractionRequest.Raised += (s, e) =>
{
Confirmation context = e.Context as Confirmation;
context.Confirmed = true;
e.Callback();
};
// Act
vm.DisplayInputDialogCommand.Execute(null);
// Assert
Assert.IsFalse(vm.IsBusy);
}
[TestMethod()]
public void DisplayInputDialogCommand_OnExecuteThrowsError_ShowsErrorDialog()
{
// Arrange
Mock<IService> mockService = new Mock<IService>();
mockService.Setup(s => s.DoSomething()).Throws(new Exception());
DelegateWorker worker = new DelegateWorker();
MyViewModel vm = new MyViewModel(mockService.Object, worker);
vm.InputDialogInteractionRequest.Raised += (s, e) =>
{
Confirmation context = e.Context as Confirmation;
context.Confirmed = true;
e.Callback();
};
InteractionRequestTestHelper<Notification> irHelper
= new InteractionRequestTestHelper<Notification>(vm.ErrorDialogInteractionRequest);
// Act
vm.DisplayInputDialogCommand.Execute(null);
// Assert
Assert.IsTrue(irHelper.RequestRaised);
}
[TestMethod()]
public void DisplayInputDialogCommand_OnExecuteThrowsError_ShowsErrorDialogWithCorrectTitle()
{
// Arrange
const string ERROR_TITLE = "Error";
Mock<IService> mockService = new Mock<IService>();
mockService.Setup(s => s.DoSomething()).Throws(new Exception());
DelegateWorker worker = new DelegateWorker();
MyViewModel vm = new MyViewModel(mockService.Object, worker);
vm.InputDialogInteractionRequest.Raised += (s, e) =>
{
Confirmation context = e.Context as Confirmation;
context.Confirmed = true;
e.Callback();
};
InteractionRequestTestHelper<Notification> irHelper
= new InteractionRequestTestHelper<Notification>(vm.ErrorDialogInteractionRequest);
// Act
vm.DisplayInputDialogCommand.Execute(null);
// Assert
Assert.AreEqual(irHelper.Title, ERROR_TITLE);
}
[TestMethod()]
public void DisplayInputDialogCommand_OnExecuteThrowsError_ShowsErrorDialogWithCorrectErrorMessage()
{
// Arrange
const string ERROR_MESSAGE_TEXT = "do something failed";
Mock<IService> mockService = new Mock<IService>();
mockService.Setup(s => s.DoSomething()).Throws(new Exception(ERROR_MESSAGE_TEXT));
DelegateWorker worker = new DelegateWorker();
MyViewModel vm = new MyViewModel(mockService.Object, worker);
vm.InputDialogInteractionRequest.Raised += (s, e) =>
{
Confirmation context = e.Context as Confirmation;
context.Confirmed = true;
e.Callback();
};
InteractionRequestTestHelper<Notification> irHelper
= new InteractionRequestTestHelper<Notification>(vm.ErrorDialogInteractionRequest);
// Act
vm.DisplayInputDialogCommand.Execute(null);
// Assert
Assert.AreEqual((string)irHelper.Content, ERROR_MESSAGE_TEXT);
}
// Helper Class
public class InteractionRequestTestHelper<T> where T : Notification
{
public bool RequestRaised { get; private set; }
public string Title { get; private set; }
public object Content { get; private set; }
public InteractionRequestTestHelper(InteractionRequest<T> request)
{
request.Raised += new EventHandler<InteractionRequestedEventArgs>(
(s, e) =>
{
RequestRaised = true;
Title = e.Context.Title;
Content = e.Context.Content;
});
}
}
}
Notes:
Another option is to use the commercial version of the TypeMock isolation (mocking) framework. This framework is ideal for legacy code or code that is otherwise not well suited for unit testing. TypeMock allows you to mock just about anything. I will not go into the specifics of how this could be used for the question at hand, but it is still worth pointing out that it is a valid option.
In .NET 4.5 use of BackgroundWorker is deprecated in favor of the async/await pattern. The use of the IDelegateWorker (or some similar) interface as described above allows your entire project to migrate to the async/await pattern without the need to modify a single ViewModel.
Update:
After implementing the technique described above, I discovered a simpler approach for .NET 4.0 or better. To unit test an asynchronous process, you need some way to detect when that process is complete or you need to be able to run that process synchronously during tests.
Microsoft introduced the Task Parallel Library (TPL) in .NET 4.0. This library provides a rich set of tools for performing asynchronous operations that go far beyond the capabilities of the BackgroundWorker class. The best way to implement an asynchronous operation is to use the TPL and then return a Task from your method under test. Unit testing an asynchronous operation implemented in this way is then trivial.
[TestMethod]
public void RunATest()
{
// Assert.
var sut = new MyClass();
// Act.
sut.DoSomethingAsync().Wait();
// Assert.
Assert.IsTrue(sut.SomethingHappened);
}
If it is impossible or impractical to expose the task to your unit test, then the next best option is to override the way that tasks are scheduled. By default tasks are scheduled to run asynchronously on the ThreadPool. You can override this behavior by specifying a custom scheduler in code. For example, the following code will run a task using the UI thread.
Task.Factory.StartNew(
() => DoSomething(),
TaskScheduler.FromCurrentSynchronizationContext());
To implement this in a way that is unit testable, pass the task scheduler in using Dependency Injection. Your unit tests can then pass in a task scheduler that performs the operation synchronously on the current thread and your production application will pass in a task scheduler that runs the tasks asynchronously on the ThreadPool.
You can even go a step further and eliminate the dependency injection by overriding the default task scheduler using reflection. This makes your unit tests a little more brittle, but is less invasive to the actual code you are testing. For a great explanation on why this works, see this blog post.
// Configure the default task scheduler to use the current synchronization context.
Type taskSchedulerType = typeof(TaskScheduler);
FieldInfo defaultTaskSchedulerField = taskSchedulerType.GetField("s_defaultTaskScheduler", BindingFlags.SetField | BindingFlags.Static | BindingFlags.NonPublic);
defaultTaskSchedulerField.SetValue(null, TaskScheduler.FromCurrentSynchronizationContext());
Unfortunately, this will not work as expected from a unit test assembly. This is because unit tests, like Console applications, do not have a SynchronizationContext and you will get the following error message.
Error: System.InvalidOperationException: The current SynchronizationContext may not be used as a TaskScheduler.
To fix this you just need to set the SynchronizationContext in your test setup.
// Configure the current synchronization context to process work synchronously.
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
This will get rid of the error, but some of your tests may still fail. This is because the default SynchronizationContext posts work asynchronously to the ThreadPool. To override this, simply subclass the default SynchronizationContext and override the Post method as follows.
public class TestSynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
Send(d, state);
}
}
With this in place, your test setup should look like the code below and all of the Tasks in your code under test will run synchronously by default.
// Configure the current synchronization context to process work synchronously.
SynchronizationContext.SetSynchronizationContext(new TestSynchronizationContext());
// Configure the default task scheduler to use the current synchronization context.
Type taskSchedulerType = typeof(TaskScheduler);
FieldInfo defaultTaskSchedulerField = taskSchedulerType.GetField("s_defaultTaskScheduler", BindingFlags.SetField | BindingFlags.Static | BindingFlags.NonPublic);
defaultTaskSchedulerField.SetValue(null, TaskScheduler.FromCurrentSynchronizationContext());
Note that this does not prevent a Task from being started with a custom scheduler. In such a case, you would need to pass that custom scheduler in using Dependency Injection and then pass in a synchronous scheduler during tests.
Good Question. I will be trying your option 3 with some slight changes.
Make InteractionRequest testable, such that the test method can choose whether to confirm or cancel the operation. So this allows testing the individual paths. You can use IoC technique (inversion of control )
Refactor all logic in DoWork & RunWorkerCompleted to separate methods, this allows to test these methods independently (if required).
Then add a new flag IsAsyncFlag to indicate if this needs to be executed asynchronously or not. Turn off the Async mode when running the tests.
There is a lot of emphasize on the test coverage. But in my experience 100% test coverage is very difficult to achieve and it can never be a synonym to code quality. Hence my focus is in identifying & writing tests that would add value to the solution.
If you have found a better approach, please share.

Categories

Resources