为什么这个asp.net mvc单元测试失败了?

时间:2010-05-22 05:58:35

标签: asp.net-mvc unit-testing

我已经编辑并简化了这个问题。

如果我的HomeController上有这个方法:

    public ActionResult Strangeness( int id )
    {
        StrangenessClass strangeness = null;

        if( id == 1 )
        {
            strangeness = new StrangenessClass() { Name="Strangeness", Desc="Really weird behavior" };
        }

        return View( strangeness );
    }

有这个课程:

public class StrangenessClass
{
    public string Name { get; set; }
    public string Desc { get; set; }
}

为什么这个单元测试失败了?

    [TestMethod]
    public void Strangeness()
    {
        HomeController controller = new HomeController();

        ViewResult result = controller.Strangeness( 1 ) as ViewResult;
        var model = result.ViewData.Model;
        result = controller.Strangeness( 2 ) as ViewResult;
        model = result.ViewData.Model;

        Assert.IsNull( model );
    }

我理解通常情况下,我会测试一个测试null条件的测试,另一个测试一个良好的条件,但我在测试我的删除控制器时遇到了这个问题。在删除测试中,我通常会获取记录,删除记录,然后再次尝试获取它。我第二次获取它时应该为null,但事实并非如此。所以,我如上所述将问题解决了。

如果这不是测试删除的正确方法,你会怎么做?你不需要确保记录真的被删除了吗?

2 个答案:

答案 0 :(得分:1)

您不应该重复使用控制器来处理多个请求,这正是您在此处所做的。

无论如何,如果你检查source code for MVC,你会发现这种行为的原因:

protected internal virtual ViewResult View(string viewName, string masterName, object model)
{
    if (model != null)
    {
        base.ViewData.Model = model;
    }
    return new ViewResult { ViewName = viewName, MasterName = masterName, ViewData = base.ViewData, TempData = base.TempData };
}

如果模型为null,则不会将其分配给ViewData.Model属性。 如果您想要正确的行为,请为第二次调用HomeController.Strangeness创建一个新的控制器。

答案 1 :(得分:0)

目前尚不清楚您在测试什么。在测试方法的“编配”部分中,您将调用第一个“删除”操作,而在“法案”部分中,您将调用第二个。所以你在测试控制器吗?如果是,那么为什么要调用编配部分中的第一个删除方法?

_stateService变量是什么?它是一个接口还是您实际在单元/集成测试中删除数据库中的记录?

所以我建议你编写多个测试,每个测试验证测试对象的精确行为,我认为是控制器。因此,您应该为要测试的不同删除操作分离单元测试。

假设_stateService是一个可以模拟的界面,我建议你设计你的控制器,你的测试看起来像这样(使用Rhino MocksMVCContrib.TestHelper):

[TestClass]
public class DevisControllerTests : TestControllerBuilder
{
    private HomeController _sut; // Subject Under Test
    private IStateService _stateServiceStub; // Dependency of the SUT

    [TestInitialize()]
    public void MyTestInitialize()
    {
        _stateServiceStub = MockRepository.GenerateStub<IStateService>();
        _sut = new HomeController(_stateServiceStub);
        InitializeController(_sut); // this method comes from the base class TestControllerBuilder
    }

    [TestMethod]
    public void HomeController_Delete_Action_Should_Fetch_State_From_Db_And_Pass_It_To_The_View()
    {
        // arrange
        var id = 4;
        var expectedState = new State();
        _stateServiceStub.Stub(x => x.GetById(id)).Return(expectedState);

        // act
        var actual = _sut.Delete(id);

        // assert
        actual
            .AssertViewRendered()
            .WithViewData<State>()
            .ShouldBe(expectedState);
    }

    [TestMethod]
    public void HomeController_Delete_Action_Handler_Should_Return_Default_View_If_Model_Null()
    {
        // act
        var actual = _sut.Delete(null);

        // assert
        actual.AssertViewRendered();
    }

    [TestMethod]
    public void HomeController_Delete_Action_Handler_Should_Return_View_If_Exception_Thrown_From_Service()
    {
        // arrange
        var model = new State();
        _stateServiceStub.Stub(x => x.Delete(model)).Throw(new Exception("oops"));

        // act
        var actual = _sut.Delete(state);

        // assert
        actual
            .AssertViewRendered()
            .WithViewData<State>()
            .ShouldBe(model);
    }


    [TestMethod]
    public void HomeController_Delete_Action_Handler_Should_Redirect_If_Model_Successfully_Deleted()
    {
        // arrange
        var model = new State();

        // act
        var actual = _sut.Delete(state);

        // assert
        actual
            .AssertActionRedirect()
            .ToAction<HomeController>(c => c.Index());

        _stateServiceStub.AssertWasCalled(x => x.Delete(model));
    }

}