Wait, isn't MVC testable by design? Yes, most of it is testable, but as for the Views, you have to resort to integration tests (which isn't bad by itself, but sometimes is not enough).
In my recent project, I needed to write a test verifying that if a user is not authenticated, she should see a login form and a registration form. Speaking in terms of implementation, this means that Html.RenderPartial
should be invoked with paths to my partial views containing these forms. Looks like a good task for TypeMock Isolator, but I prefer working with CThru. Here's the test code (forgive me for not following the strict rules, such as one assert per test, this is just for clarity):
Wait, isn't MVC testable by design? Yes, most of it is testable, but as for the Views, you have to resort to integration tests (which isn't bad by itself, but sometimes is not enough).
In my recent project, I needed to write a test verifying that if a user is not authenticated, she/he should see a login form and a registration form. Speaking in terms of implementation, this means that Html.RenderPartial
should be invoked with paths to my partial views containing these forms. Looks like a good task for TypeMock Isolator, but I prefer working with CThru. Here's the test code (forgive me for not following the strict rules, such as one assert per test, this is just for clarity):
Imports WebTests.Infrastructure
Imports Ivonna.Framework
Imports MbUnit.Framework
Namespace Membership
<TestFixture(), RunOnWeb()> _
Public Class OwnersPage_NotLoggedIn
<Test()> _
Public Sub ContainsLoginPartial()
'Arrange
Dim spy As New RenderPartialSpy
CThru.CThruEngine.AddAspect(spy)
Dim session As New TestSession
'Act
session.ProcessRequest(New WebRequest("Owners"))
'Assert
Assert.Contains(spy.ViewNames, "~/Views/Account/Login.ascx")
Assert.Contains(spy.ViewNames, "~/Views/Account/Register.ascx")
End Sub
End Class
End Namespace
The RenderPartialSpy
class can be reused (this is why I prefer CThru), here's the code for it:
Imports CThru
Imports System.Web.Mvc.Html
Namespace Infrastructure
Public Class RenderPartialSpy
Inherits Aspect
Public Overrides Function ShouldIntercept(
ByVal info As CThru.InterceptInfo) As Boolean
Return info.TypeName.Contains(
"RenderPartialExtensions") AndAlso info.MethodName = "RenderPartial"
End Function
Private _viewNames As New List(Of String)
Public ReadOnly Property ViewNames() As IList(Of String)
Get
Return _viewNames
End Get
End Property
Public Overrides Sub MethodBehavior(ByVal e As CThru.DuringCallbackEventArgs)
Me.ViewNames.Add(e.ParameterValues(1))
MyBase.MethodBehavior(e)
End Sub
End Class
End Namespace
Here I just set it to intercept all calls on the RenderPartialExtensions
class with the method name "RenderPartial
". When the call is intercepted, I save the value of the second argument (remember this is an extension method) in my list. At the Assert stage, I'll be able to retrieve all saved values and check them.
Imports WebTests.Infrastructure
Imports Ivonna.Framework
Imports MbUnit.Framework
Namespace Membership
<TestFixture(), RunOnWeb()> _
Public Class OwnersPage_NotLoggedIn
<Test()> _
Public Sub ContainsLoginPartial()
'Arrange
Dim spy As New RenderPartialSpy
CThru.CThruEngine.AddAspect(spy)
Dim session As New TestSession
'Act
session.ProcessRequest(New WebRequest("Owners"))
'Assert
Assert.Contains(spy.ViewNames, "~/Views/Account/Login.ascx")
Assert.Contains(spy.ViewNames, "~/Views/Account/Register.ascx")
End Sub
End Class
End Namespace
The RenderPartialSpy
class can be reused (this is why I prefer CThru), here's the code for it:
Imports CThru
Imports System.Web.Mvc.Html
Namespace Infrastructure
Public Class RenderPartialSpy
Inherits Aspect
Public Overrides Function ShouldIntercept(
ByVal info As CThru.InterceptInfo) As Boolean
Return info.TypeName.Contains(
"RenderPartialExtensions") AndAlso info.MethodName = "RenderPartial"
End Function
Private _viewNames As New List(Of String)
Public ReadOnly Property ViewNames() As IList(Of String)
Get
Return _viewNames
End Get
End Property
Public Overrides Sub MethodBehavior(ByVal e As CThru.DuringCallbackEventArgs)
Me.ViewNames.Add(e.ParameterValues(1))
MyBase.MethodBehavior(e)
End Sub
End Class
End Namespace
Here I just set it to intercept all calls on the RenderPartialExtensions
class with the method name "RenderPartial
". When the call is intercepted, I save the value of the second argument (remember this is an extension method) in my list. At the Assert stage, I'll be able to retrieve all saved values and check them.