I'm creating a WPF application using the Composite Application Library (CAL), aka PRISM. I'm also using MVVM (Model-View-ViewModel). To prevent background processing from blocking the UI and making it unresponsive, I want to have the view model make most work calls on a background thread. To do this, I'm using the .NET ThreadPool object and making calls back to the UI using a dispatcher object.
I also need to be able to write unit tests for the view models. The basic design of the view model is already great for unit testing, since I'm using a IOC container (Unity) to inject needed interfaces into it. So it's very easy to passed mocked interfaces instead when unit testing.
The specific problem I'm having right now is with testing a login view model. The view model has properties for username and password that are set on the view (a WPF user control) and bound to the model. A button in the view is bound to a command (an ICommand) that then verifies the provided credentials against 1) Active Directory (to verify that the user exists in AD) and 2) an application-specific service that verifies that the user has an active account in the application. These calls are made to a LoginService class that exists in the application module (a CAL module).
A simple expression of this in a unit test, using MSTest and NMock, is:
[TestMethod]
public void OnSuccessfulLoginCalledWhenCredentialsValidAndUserExists()
{
Mockery mockery = new Mockery();
IEventAggregator aggregator = mockery.NewMock<IEventAggregator>();
ILoginController controller = mockery.NewMock<ILoginController>();
ILoginService service = mockery.NewMock<ILoginService>();
LoginViewModel model = new LoginViewModel(aggregator, controller, service);
Expect.Once.On(service).Method("LoginCredentialsAreValid").With("test", "test").Will(Return.Value(true));
Expect.Once.On(service).Method("UserExistsInApplication").With("test").Will(Return.Value(true));
Expect.Once.On(controller).Method("OnSuccessfulLogin");
model.LoginRequestCommand.Execute(new LoginRequestRequest("test", "test"));
mockery.VerifyAllExpectationsHaveBeenMet();
}
The code that actually verifies the credentials looks like this (simplified for clarity):
ThreadPool.QueueUserWorkItem(delegate
{
Dispatcher.BeginInvoke((ThreadStart)delegate
{
// publish an event so the ui can show the wait cursor and progress bar
eventAggregator.GetEvent<BusyEvent>().Publish(true);
});
if (loginService.LoginCredentialsAreValid(loginRequest.UserName, loginRequest.Password))
{
if (loginService.UserExistsInApplication(loginRequest.UserName))
{
Dispatcher.BeginInvoke((ThreadStart)delegate
{
// tell the ui the operation has completed
eventAggregator.GetEvent<BusyEvent>().Publish(false);
// call the controller so it can hide login screen and load other views
loginController.OnSuccessfulLogin();
});
}
else
{
Dispatcher.BeginInvoke((ThreadStart)delegate
{
// display an error message to the user
});
}
}
else
{
Dispatcher.BeginInvoke((ThreadStart)delegate
{
// display an error message to the user
});
}
});
In real usage, this code works perfectly, keeping the UI responsive while carrying out background processing on a separate thread. The problem comes in when I run the unit test. When the call to loginService.LoginCredentialsAreValid is made, an exception is raised by NMock saying that there was un expected invocation of the method, even though I clearly listed that expectation in the test code.
A coworker suggested that I might need a Thread.Sleep call right before VerifyAllExpectationsHaveBeenMet, to make sure that the background thread had time to complete. This did not affect the outcome at all.
As a test, I replaced the working code with a bare-bones, non-threaded implementation which blocks the UI, as shown:
if (loginService.LoginCredentialsAreValid(loginRequest.UserName, loginRequest.Password))
{
if (loginService.UserExistsInApplication(loginRequest.UserName))
{
loginController.OnSuccessfulLogin();
}
}
The test then runs to completion and passes.
So, can NMock verify expected method calls when those calls occur on a background thread? Or is this not possible? I hope there is a way to do this since much of the application's calls will need to be handled this way.
I'm using NMock2 (2.1.3641.27570) with .NET 3.5 SP1. Any insight would be appreciated.