I am not a regular with the MVVM pattern and this is basically my first time playing with it.
What I used to do ("normal" WPF), was creating my Views with a Business layer and perhaps a datalayer (which usually contains my entities created by a service or the Entity Framework).
Now after some toying I created a standard template from MVVM Light and did this:
Locator:
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IUserService, DesignUserService>();
}
else
{
SimpleIoc.Default.Register<IUserService, IUserService>();
}
SimpleIoc.Default.Register<LoginViewModel>();
}
public LoginViewModel Login
{
get
{
return ServiceLocator.Current.GetInstance<LoginViewModel>();
}
}
}
Login ViewModel:
public class LoginViewModel : ViewModelBase
{
private readonly IUserService _userService;
public RelayCommand<Object> LoginCommand
{
get
{
return new RelayCommand<Object>(Login);
}
}
private string _userName;
public String UserName
{
get { return _userName; }
set
{
if (value == _userName)
return;
_userName = value;
RaisePropertyChanged("UserName");
}
}
/// <summary>
/// Initializes a new instance of the LoginViewModel class.
/// </summary>
public LoginViewModel(IUserService userService)
{
_userService = userService;
_closing = true;
}
private void Login(Object passwordBoxObject)
{
PasswordBox passwordBox = passwordBoxObject as PasswordBox;
if (passwordBox == null)
throw new Exception("PasswordBox is null");
_userService.Login(UserName, passwordBox.SecurePassword, result =>
{
if (!result)
{
MessageBox.Show("Wrong username or password");
}
});
}
}
Binding and commands work fine so there is no questions. Business mockup class for design and test time:
public class DesignUserService : IUserService
{
private readonly User _testUser;
private readonly IList<User> _users;
public void Login(String userName, SecureString password, Action<Boolean> callback)
{
var user = _users.FirstOrDefault(u => u.UserName.ToLower() == userName.ToLower());
if (user == null)
{
callback(false);
return;
}
String rawPassword = Security.ComputeHashString(password, user.Salt);
if (rawPassword != user.Password)
{
callback(false);
return;
}
callback(true);
}
public DesignUserService()
{
_testUser = new User
{
UserName = "testuser",
Password = "123123",
Salt = "123123"
};
_users = new List<User>
{
_testUser
};
}
}
UserData is a static class which makes calls to the database (Entity Framework).
Now I have my test:
[TestClass]
public class Login
{
[TestMethod]
public void IncorrectUsernameCorrectPassword()
{
IUserService userService = new DesignUserService();
PasswordBox passwordBox = new PasswordBox
{
Password = "password"
};
userService.Login("nonexistingusername", passwordBox.SecurePassword, b => Assert.AreEqual(b, false));
}
}
Now my test is not on the ViewModel itself but directly to the Business layer.
Basically I have 2 questions:
Am I on the right path, or is there a fundamental flaw in my pattern implementation?
How can I test my ViewModel?
Your view model has one relevant piece of code worth testing, which is Login
method. Given that it's private, it should be tested it via LoginCommand
.
Now, one might ask, what is the purpose of testing command when you already have test for underlying business logic? The purpose is to verify that business logic is called and with correct parameters.
How do one goes with such test? By using mock. Example with FakeItEasy:
var userServiceFake = A.Fake<IUserService>();
var testedViewModel = new LoginViewModel(userServiceFake);
// prepare data for test
var passwordBox = new PasswordBox { Password = "password" };
testedViewModel.UserName = "TestUser";
// execute test
testedViewModel.LoginCommand.Execute(passwordBox);
// verify
A.CallTo(() => userServiceFake.Login(
"TestUser",
passwordBox.SecurePassword,
A<Action<bool>>.Ignored)
).MustHaveHappened();
This way you verify that command calls business layer as expected. Note that Action<bool>
is ignored when matching parameters - it's difficult to match Action<T>
and Func<T>
and usually not worth it.
Few notes:
Action
argument)INotifyPropertyChanged
properties (UserName
in your case) - that event is raised when property value changes. Since this is lot of boilerplate code, using tool /library to automate this process is highly suggested.