带有私有构造函数的c#模拟对象,通过静态工厂方法初始化

时间:2014-11-25 09:58:34

标签: c# unit-testing mocking moq

我正在测试以下控制器:

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;
    }

我正在考虑模拟 IUserManager.GetByCredentials 方法,因为我只想看到 MyController.GetByCredentials 方法按预期工作。问题是 User 类无法直接实例化,因此我无法模拟User对象,因为构造函数是私有的:

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

}

我正在使用Moq框架,但我对不同的解决方案持开放态度。在这种情况下,我宁愿避免创建测试数据,因为它依赖于数据库的初始化,服务器等等 - 然后它就不再是单元测试了。你有过像这样的问题吗?你如何解决?谢谢。

2 个答案:

答案 0 :(得分:3)

您无需模拟User - 您可以使用真正的User类。你只需要模拟(或伪造)IUserManager。您的模拟/假冒IUserManager可以使用User.CreateUser创建要返回给您的控制器的User个对象。

除非User类本身“知道”数据库,否则应该没问题。抵制模仿一切的诱惑 - 你只需要摆脱那些难以编写测试的依赖。

现在你已经写过你的User私有构造函数“做了很多事情” - 如果这太过深了,你应该重新设计User,这样就更简单了...你的用户经理应该负责那里正在进行的一些工作。

虽然将世界完全分解为服务(如用户管理器)和愚蠢的数据对象(如用户)是过于简单化的,但如果设计合理自然将其自身分割,则会使事情变得更容易尊重。

答案 1 :(得分:3)

为了测试您的MyController,您将嘲笑IUserManager而不是User,这样您就可以做到这样的事情:

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());

模拟/伪造对象的要点是避免数据库/ Web服务调用。您的AccountUser类应该是poco类 - 例如只包含对自身起作用的方法和属性,没有数据库或Web服务调用等,因此它们实际上不需要模拟。