草庐IT

c# - 如何对 MVC ASP.Net 返回正确的 View 进行单元测试?

coder 2024-05-29 原文

我是 MVC、单元测试、模拟和 TDD 的新手。我正在尝试尽可能地遵循最佳实践。

我已经为 Controller 编写了单元测试,但我无法测试是否返回了正确的 View 。如果我使用 ViewResult.ViewName,如果我没有在 Controller 中指定 View 名称,测试总是会失败。如果我确实在 Controller 中指定了 ViewName,那么即使 View 不存在,测试也会通过。

我也试过测试 Response.Status 代码,但是它总是返回 200(代码取自 Darin Dimitrov 对 MVC3 unit testing response code 的回答)。我的目标是在创建新 View 时进行经典的红色、绿色重构,并在上线时避免 404 和 System.InvalidOperationException 错误,这可能吗?

下面的代码。

public class BugStatusController : Controller
{
    public ActionResult Index(){
        return View(); // Test always fails as view name isn’t specified even if the correct view is returned.
    }

    public ActionResult Create(){
        return View("Create"); // Test always passes as view name is specified even if the view doesn’t exist.
    }
}

[TestFixture]
public class BugStatusTests
{    
    private ViewResult GetViewResult(Controller controller, string controllerMethodName){
        Type type = controller.GetType();
        ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);

        object instance = constructor.Invoke(new object[] {});
        MethodInfo[] methods = type.GetMethods();

        MethodInfo methodInfo = (from method in methods
                                where method.Name == controllerMethodName
                                                    && method.GetParameters().Count() == 0
                                select method).FirstOrDefault();

        Assert.IsNotNull(methodInfo, "The controller {0} has no method called {1}", type.Name, controllerMethodName);

        ViewResult result = methodInfo.Invoke(instance, new object[] {}) as ViewResult;

        Assert.IsNotNull(result, "The ViewResult is null, controller: {0}, view: {1}", type.Name, controllerMethodName);

        return result;
    }

    [Test]
    [TestCase("Index", "Index")]
    [TestCase("Create", "Create")]
    public void TestExpectedViewIsReturned(string expectedViewName, string controllerMethodName){
        ViewResult result = GetViewResult(new BugStatusController(), controllerMethodName);

        Assert.AreEqual(expectedViewName, result.ViewName, "Unexpected view returned, controller: {0}, view: {1}", CONTROLLER_NAME, expectedViewName);
    }

    [Test]
    [TestCase("Index", "Index")]
    [TestCase("Create", "Create")]
    public void TestExpectedStatusCodeIsReturned(string expectedViewName, string controllerMethodName)
    {
        var controller = new BugStatusController();
        var request = new HttpRequest("", "http://localhost:58687/", "");
        var response = new HttpResponse(TextWriter.Null);
        var httpContext = new HttpContextWrapper(new HttpContext(request, response));
        controller.ControllerContext = new ControllerContext(httpContext, new RouteData(), controller);

        ActionResult result = GetViewResult(controller, controllerMethodName);

        Assert.AreEqual(200, response.StatusCode, "Failed to load " + expectedViewName + " Error: "  + response.StatusDescription);
    }
}

最佳答案

I’m new to MVC, Unit Testing, Mocking and TDD. I’m trying to follow best practice as closely as possible.

我很高兴越来越多的开发人员开始为他们的代码编写单元测试,所以恭喜您走上了正确的道路。

if I don’t specify the view name in the controller. If I do specify the ViewName in the controller the test always passes, even if the view doesn’t exist.

当您不在 View 中指定 View 名称时方法指示 MVC 引擎呈现默认 View ,例如

public ActionResult Index() { return View(); }

以上代码将返回一个空的 View 名称,这意味着呈现的 View 将是操作的名称,在本例中它将是Index

因此,如果您希望测试某个操作是否返回默认 View ,则必须测试返回的 View 名称是否为空

Test always passes as view name is specified even if the view doesn't exist.

为了解释这里发生了什么,我将首先解释操作过滤器的工作原理。

基本上有四种类型的过滤器

  • 异常过滤器
  • 授权过滤器
  • Action 过滤器
  • 结果过滤器

我将专注于操作和结果过滤器

操作过滤器是用 IActionFilter 定义的界面

public interface IActionFilter
{
    // Summary:
    //     Called after the action method executes.
    //
    void OnActionExecuted(ActionExecutedContext filterContext);
    //
    // Summary:
    //     Called before an action method executes.
    //
    void OnActionExecuting(ActionExecutingContext filterContext);
}

结果过滤器是用 IResultFilter 定义的界面

public interface IResultFilter
{
    // Summary:
    //     Called after an action result executes.
    //
    void OnResultExecuted(ResultExecutedContext filterContext);
    //
    // Summary:
    //     Called before an action result executes.
    //
    void OnResultExecuting(ResultExecutingContext filterContext);
}

当执行 Controller 的操作时,将按此特定顺序执行以下过滤器:

IActionFilter.OnActionExecuting
IActionFilter.OnActionExecuted
IResultFilter.OnResultExecuting
IResultFilter.OnResultExecuted

当一个 Action 被执行时,另一个组件负责处理你的ActionResult 从您的操作返回并呈现正确的 HTML 以将其发送回客户端,这是处理结果的时间

这种清晰的关注点分离是美妙之处,也是让我们对 Controller 的操作进行单元测试的关键,否则,如果它们耦合在一起,我们将无法单独对操作结果进行单元测试

现在 RazorViewEngine 在执行操作后(处理结果时)尝试查找 View ,这就是为什么即使物理 View 不存在,您的测试也会返回 true。这是预期的行为,请记住您需要单独测试 Controller 的操作。只要您在您的单元测试中声明预期的 View 已呈现,您就完成了单元测试。

如果你想断言物理 View 存在,那么你会谈论一些特定的集成测试:功能测试或用户验收测试 - 这些类型的测试需要使用浏览器实例化你的应用程序它们不是以任何方式进行单元测试

现在您可以手动编写单元测试(如果您正在进入单元测试领域,这是一个很好的练习),但是,我想向您推荐几个可以帮助您的 MVC 测试框架非常快地编写单元测试

关于这些框架的一些个人意见

根据我的经验,MVC Contrib 比 Fluent MVC Testing 具有更多的功能,但是,由于我使用的是 MVC 4,我无法在 Visual Studio 2012 中使用它,所以我结合使用了两者(这在我找到更好的方法之前是一个肮脏的解决方法)

这是我的做法:

var testControllerBuilder = new TestControllerBuilder(); // this is from MVC Contrib
var controller = new MoviesController(
    this.GetMock<IMovieQueryManager>().Object);

testControllerBuilder.InitializeController(controller); // this allows me to use the Session, Request and Response objects as mock objects, again this is provided by the MVC Contrib framework

// I should be able to call something like this but this is not working due to some problems with DLL versions (hell DLL's) between MVC Controb, Moq and MVC itself
// testControllerBuilder.CreateController<MoviesController>();

controller.WithCallTo(x => x.Index(string.Empty)).ShouldRenderDefaultView(); // this is using Fluent MVC Testing

// again instead of the above line I could use the MVC Contrib if it were working....
// var res = sut.Index(string.Empty);
// res.AssertViewRendered().ForView("Index");

我希望这对您有所帮助 =) 编码愉快!

关于c# - 如何对 MVC ASP.Net 返回正确的 View 进行单元测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13298792/

有关c# - 如何对 MVC ASP.Net 返回正确的 View 进行单元测试?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  4. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  5. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  6. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  7. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  8. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  9. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  10. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

随机推荐