【问题标题】:C# MVC Mock Model through interface brokes the controllerC# MVC Mock Model通过接口破坏了控制器
【发布时间】:2017-06-13 02:58:31
【问题描述】:

我在控制器中有这个方法:

[HttpPatch]
[AllowAdminOnly]
public JsonResult EditUser(User _user)
{
    try
    {
        if (ModelState.IsValid)
        {
            _user.Edit();
        }
        else
        {
            string error_messages = "";
            foreach (var e in ModelState.Select(x => x.Value.Errors).Where(y => y.Count > 0).ToList())
            {
                error_messages += e[0].ErrorMessage + "\n";
            }
            throw new Exception(error_messages);
        }
        return MessageHelper(@Resources.Global.success, @Resources.Users.success_editing_user, "success");
    }
    catch (Exception err)
    {
        return ErrorHelper(@Resources.Global.error, @Resources.Users.error_editing_user, err.Message);
    }
}

为了对该方法进行单元测试,我发现参考文献 (...) 说我应该“模拟” _user.Edit();。看起来不错,它会避免将数据保存到数据库,并且会使测试更快。

然后我的测试(对于有效用户)变为:

[TestMethod]
public void UserController_EditUser_Valid()
{
    // Arrange
    FundController controller = new UserController();
    controller.ControllerContext = TestModelHelper.AdminControllerContext();

    var _user = new Mock<Fund>();
    _user.SetupGet(f => f.id).Returns(1);
    _user.SetupGet(f => f.name).Returns("User name");
    _user.SetupGet(f => f.nickname).Returns("User nickname");
    _user.SetupGet(f => f.active).Returns(true);
    _user.Setup(f => f.Edit()).Callback(() => {}).Verifiable();

    // Act
    var result = (JsonResult)controller.EditUser(_user.Object);
    SimpleMessage resultMessage = m_serializer.Deserialize<SimpleMessage>(m_serializer.Serialize(result.Data));

    // Assert
    Assert.IsNotNull(resultMessage, "Result must not be null");
    Assert.IsTrue(resultMessage.status.Equals("success"), "status must be 'success'");
}

但是当我这样做时,我收到以下错误:

Test Name:  UserController_EditUser_Valid
Test FullName:  Pmanager.Tests.Controllers.UserControllerTest.UserController_EditUser_Valid
Test Source:    ...\Pmanager\Pmanager.Tests\Controllers\UserControllerTest.cs : line 95
Test Outcome:   Failed
Test Duration:  0:00:00,0179908

Result StackTrace:  
em Moq.Mock.ThrowIfCantOverride(Expression setup, MethodInfo method)
   em Moq.Mock.<>c__DisplayClass66_0`2.<SetupGet>b__0()
   em Moq.PexProtector.Invoke[T](Func`1 function)
   em Moq.Mock.SetupGet[T,TProperty](Mock`1 mock, Expression`1 expression, Condition condition)
   em Moq.Mock`1.SetupGet[TProperty](Expression`1 expression)
   em Pmanager.Tests.Controllers.UserControllerTest.UserController_EditUser_Valid() na ...\Pmanager\Pmanager.Tests\Controllers\UserControllerTest.cs:linha 100
Result Message: 
Test method Pmanager.Tests.Controllers.UserControllerTest.UserController_EditUser_Valid threw exception: 
System.NotSupportedException: Valid setup on a non-virtual (overridable in VB) member: f => f.id

我发现一些关于 (...) 的文档说我应该在创建 "mock" 时使用接口而不是类。

所以,我创建了一个界面:

public interface IUser
{
    int id { get; set; }
    string name { get; set; }
    string nickname { get; set; }
    bool active { get; set; }
    void Edit();
}

然后我更改了控制器的方法签名IUser 的所有内容:

public JsonResult EditUser(IUser _user);

测试“mock”声明:

var _user = new Mock<IUser>();

等等。

现在测试可以了,但是实际控制器编辑用户的方法不行!

如何在不破坏控制器真正功能的情况下将所有这些东西放在一起?

【问题讨论】:

  • var _user = new Mock&lt;IFund&gt;();var _user = new Mock&lt;IUser&gt;();
  • var _user = new Mock&lt;IUser&gt;();,对不起
  • 您正在向 IUser 传递一个模拟实现。所以您还需要提供 Edit 的模拟实现!您现有的编辑将不起作用。你期待吗?
  • 不,我原以为它不会工作。我不希望触发此方法。我的意图是实际测试其他所有内容。
  • 我知道这是一个奇怪的问题,但是 User 实现了 IUser 吗?

标签: c# asp.net-mvc unit-testing nunit moq


【解决方案1】:

更新控制器以使用依赖项来更新用户模型。从模型本身中删除该功能。模型应该是 POCO/DTO。

public class UserController : Controller {
    readonly IUserService userService;

    public UserController(IUSerService userService) {
        this.userService = userService;
    }

    [HttpPatch]
    [AllowAdminOnly]
    public JsonResult EditUser(User _user) {
        try {
            if (ModelState.IsValid) {
                userService.Edit(user);
            } else {
                string error_messages = "";
                foreach (var e in ModelState.Select(x => x.Value.Errors).Where(y => y.Count > 0).ToList()) {
                    error_messages += e[0].ErrorMessage + "\n";
                }
                throw new Exception(error_messages);
            }
            return MessageHelper(@Resources.Global.success, @Resources.Users.success_editing_user, "success");
        } catch (Exception err) {
            return ErrorHelper(@Resources.Global.error, @Resources.Users.error_editing_user, err.Message);
        }
    }
}

IUserService 类似于

public interface IUserService {
    void Edit(User user);
}

及其生产实现将执行所需的操作。请记住使用您使用的任何 DI 注册抽象和实现。

然后,测试将模拟独立运行所需的依赖项。

[TestMethod]
public void UserController_EditUser_Should_Be_Valid() {
    // Arrange    
    var _user = new User {
        id = 1,
        name = "User name",
        nickname = "User nickname",
        active = true
    };

    var mockService = new Mock<IUserService>();
    mockService .Setup(m => m.Edit(_user)).Verifiable();

    var controller = new UserController(mockService.Object);
    controller.ControllerContext = TestModelHelper.AdminControllerContext();

    // Act
    var result = controller.EditUser(_user) as JsonResult;

    // Assert
    Assert.IsNotNull(result, "Result must not be null");        
    mockService.Verify(); // verify that the service was call successfully. 
}

【讨论】:

  • 我知道了,它成功了。另外,感谢 POCO/DTO 术语,我不知道这一点。此外,我不知道我应该将mockService.Verify(); int 和。非常感谢
【解决方案2】:

嗯,我仍然怀疑做这一切的正确方法。

但我想出了一种让两者都起作用的方法。它是:

  • 保持控制器的方法签名应为:public JsonResult EditUser(User _user);
  • 在将 mock 传递给控制器​​的方法时对其进行解析:var result = (JsonResult)controller.EditUser(_user.Object as User);

虽然它有效,但我不确定整个东西是否和谐...... :(

希望有人能给出更好的答案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-10
    • 2014-12-13
    • 1970-01-01
    • 2016-03-10
    • 2019-02-18
    • 1970-01-01
    • 2013-01-03
    相关资源
    最近更新 更多