Why does this asp.net mvc unit test fail?
I have edited and simplified this question a lot.
If I have this method on my HomeController:
public ActionResult Strangeness( int id )
{
StrangenessClass strangeness = null;
if( id == 1 )
{
strangeness = new StrangenessClass() { Name="Strangeness", Desc="Really weird behavior" };
}
return View( strangeness );
}
And have this class:
public class StrangenessClass
{
public string Name { get; set; }
public string Desc { get; set; }
}
Why does this unit test fail?
[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 );
}
I understand that normally, I would have one test to test the null condition and another to test a good condition, but I ran into this problem while testing my delete controller. On a delete test, I would normally fetch the record, delete the record, and then attempt 开发者_如何学Goto fetch it again. It should be null the second time I fetch it, but it wasn't. So, I boiled the problem down as described above.
If this is not the proper way to test deletes, how would you do it? Don't you need to make sure that the record was actually deleted?
You should not reuse a controller to handle multiple requests, which is exactly what you are doing here.
Anyway, if you check the source code for MVC you'll find the reason for this behavior:
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 };
}
If the model is null, it's not assigned to the ViewData.Model property.
If you want the correct behaviour, create a new controller for your second call to HomeController.Strangeness
.
It is not clear what you are testing. In the Arrange section of your test method you are calling the first Delete action and in the Act section you are calling the second. So are you testing the controller? If yes then why are you calling the first Delete method in the Arrange section?
Also what's the _stateService
variable? Is it an interface or are you actually deleting records in the database in your unit/integration test?
So I would recommend you writing multiple tests, each one verifying a precise behavior of the subject under test which I assume is the controller. So you should separate unit tests for the different Delete actions you are testing.
Assuming that _stateService
is an interface that could be mocked, which is how I would recommend you design your controller, your test could look like this (using Rhino Mocks and MVCContrib.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));
}
}
精彩评论