Reputation: 4610
I am testing the following controller:
public class MyController : ApiController
{
private readonly IUserManager _users;
private int num = 0;
public MyController(IUserManager users)
{
_users = users;
}
public User GetByCredentials(string username, string pass)
{
var account = new Account(username, pass);
User u = _users.GetByCredentials(num, account);
return u;
}
I was thinking to mock the IUserManager.GetByCredentials method, as I only want to see that MyController.GetByCredentials method works as expected. The problem is that the User class cannot be instantiated directly, so I cannot mock a User object, because the constructor is private:
public class User
{
// private attributes here
protected User()
{
// do a lot of stuff here, call other objects to initialize attributes
}
private User(int num, string id, string userid, Account account)
: this()
{
// do other stuff here with the params
}
public static User CreateUser(int num, string id, string userid, Account account)
{
return new User(num, id, userid, account);
}
// and so on
}
I am using the Moq framework, but I am open to different solutions. I would prefer to avoid the creation of test data in this case, because it depends on database the initialization, servers and so on - and then it would not be a unit test anymore. Have you ever had an issue like this? How do you deal with it? Thanks.
Upvotes: 1
Views: 4424
Reputation: 16423
In order to test your MyController
, you will be mocking IUserManager
not User
so you will be able to do something like this:
var mockUserManager = new Mock<IUserManager>();
// Configure the User to be returned by calling GetByCredentials
mockUserManager
.Setup(x => x.GetByCredentials(It.IsAny<int>(), It.IsAny<Account>())
.Returns(User.CreateUser(1, "foo", "username", new Account());
var controller = new MyController(mockUserManager.Object);
var user = controller.GetByCredentials("username", "password");
Assert.NotNull(user);
mockUserManager.Verify(x => x.GetByCredentials(It.IsAny<int>(), It.IsAny<Account>(), Times.Once());
The point of mock/fake objects is to avoid database/web service calls. Your Account
and User
classes should be poco classes - e.g. only contain methods and properties which act upon itself, no database or web service calls etc so they don't actually need mocking.
Upvotes: 3
Reputation: 1503270
You don't need to mock User
- you can use the real User
class. You only need to mock (or fake) IUserManager
. Your mock/fake IUserManager
can use User.CreateUser
to create the User
objects to be returned to your controller.
Unless the User
class itself "knows" about the database, that should be fine. Resist the temptation to mock everything - you only need to get rid of dependencies that make it hard to write tests.
Now you've written that your User
private constructor "does a lot of stuff" - if that reaches out too far, you should redesign User
so it's simpler... your user manager should possibly be responsible for some of the work that's going on there.
While it's an oversimplification to completely split the world into services (like the user manager) and dumb data objects (like the user), it makes things much easier if a design reasonably naturally splits itself in that respect.
Upvotes: 3