哪种模型绑定方法在ASP.NET MVC中具有最佳单元测试语义?

时间:2009-02-06 15:46:30

标签: asp.net-mvc unit-testing modelbinders

解释

在ASP.NET MVC中,有两种方法可以在动作中进行模型绑定。我们称之为“绑定参数方式”和“UpdateModel方式”。它们两者几乎完全相同,它们几乎完全相同:

    public ActionResult UpdateWithBindArguments(Foo model)
    {
        Repository.Update(model);
        // error handling removed
        return RedirectToAction(...)
    }

    public ActionResult UpdateWithUpdateModel()
    {
        Foo model; 
        UpdateModel(model); // part of MVC framework
        Repository.Update(model);
        // error handling removed
        return RedirectToAction(...)
    }

正如我所说,这些几乎完全相同。第一个可能稍微更具可读性,但我可以克服它。

两种测试方法

重要的差异,我认为是 你对它们进行单元测试:

    [TestMethod]
    public void TestUpdateWithBindArguments()
    {
       var model = new Foo() { PropertyName = "Bar" };
       var controller = new FooController();

       var result = controller.UpdateWithBindArguments(model);

       // assert
    }

    [TestMethod]
    public void TestUpdateWithUpdateModel()
    {
       var formData = new FormCollection() { { "PropertyName", "Bar" } };
       var controller = new FooController();
       controller.ValueProvider = formData.ToValueProvider();

       var result = controller.UpdateWithUpdateModel();

       // assert
    }

第一种方法使用强大的静态类型构建模型。第二个构造提交了具有名称/值对的用户数据。我发现第一种方法更容易阅读,但第二种方法更接近于网站调用控制器时实际发生的情况。

出于这个问题范围之外的原因,我从来没有说过应该使用lambda表达式而不是字符串来构建用于模型绑定的aspx页面。我很乐意与你进行讨论,但我们不要在这里做。出于这个问题的目的,让我们理所当然地认为我将使用内置的HtmlHelper方法,这些方法采用字符串而不是对其进行扩展,这些方法采用lambda表达式。因此,第二种方法使用名称/值对作为针对aspx页面的“动态”性质的非正式测试具有一定的价值。当然,它不会取代针对网站的集成测试。

问题(最后!)

我看到两种方法的优点和缺点。我的问题是,是否有一个非常强烈的论据支持我遗漏的一种方法?

修改我正在寻找客观答案。我正在寻找非显而易见的原因为什么一种方法比另一种方法更好,而不是试图进行民意调查。

4 个答案:

答案 0 :(得分:3)

在测试方面,在我看来,将对象直接传递给action方法更自然。没有必要填充ValueProviderDictionary。

需要另一种方法的原因是您可能需要控制要绑定的对象的实例化。 DefaultModelBinder只是查找默认构造函数并调用它。

但在某些情况下,您可能需要在将对象绑定到表单值之前自己创建对象。这就是UpdateModel发挥作用的地方。

答案 1 :(得分:0)

我倾向于像这样写

public ActionResult Update(int id, string name)
{
  Person person = PersonRepository.GetByID(id);
  //Do any error handling
  person.Name = name;
  //Do any error handling
  PersonRepository.Update(person);
}

然后进行测试(使用RhinoMocks)

Person person = new Person();
var mockPersonRepository = MockRepository.GenerateMock<IPersonRepository>();
mockPersonRepository.Expect(x => x.GetByID(1)).Return(person);
mockPersonRepository.Expect(x => x.Update(person));

var controller = new MyController(mockPersonRepository);
controller.Update(1, "Hello");
Assert("Hello", person.Name);
mockPersonRepository.VerifyAllExpectations();

所以我的答案是,以上都不是: - )

答案 2 :(得分:0)

我总是选择透明度来保持测试的噪音,所以第一种方法是更好的IMO。

只是好奇你喜欢HTML生成表达式的魔术字符串。它是对编译器的隐形,对重构的抵制,还是你讨厌intellisense。 :)哦,我现在已经完成了,开始了一个非主题辩论。

答案 3 :(得分:0)

我喜欢绑定到模型对象的第一个选项。这使控制器从模型和视图的细节中解放出来。因此,我可以根据需要更改模型和视图,而不会影响我的控制器。因此,测试隔离也变得更容易。

对于仅涉及域的一小部分的复杂域模型或视图,我创建了一个视图模型,用于对视图中的数据进行建模,而不是对我域中的数据进行建模。然后我编写映射代码或使用对象映射器将视图模型映射到我的域模型。这减少了我的视图和我的域之间的耦合。

我必须不同意Peter Morris将各个参数传递给控制器​​方法。这一开始看起来不错,但一旦开始使用大型表单,很快就会变得很痛苦。此外,这增加了控制器和视图之间的耦合。