search
top

Discovering Typemock

Over Christmas I started a little MVC app and because I want it to be a production quality app, and live for a long time, I decided to write unit tests for the whole thing. I don’t know if it will be 100% coverage, but ideally, I’d like it to be close.

So, before I even wrote a line of my own code, I started writing tests for the generated code, and as you might expect, it wasn’t long before I started running into static methods which cannot be mocked with Moq (the mocking framework I was using).

Here’s an example of an action method I tested:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
  if (ModelState.IsValid)
  {
    if (Membership.ValidateUser(model.UserName, model.Password))
  {
  FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
  if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
  && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
 
// If we got this far, something failed, redisplay form
return View(model);
}

Notice, the Membership.ValidateUser() & FormsAuthentication.SetAuthCookie() methods are static and untestable.

The strategy I found for testing these methods was to create wrappers, so I created the following classes.

1
2
3
4
5
6
7
public class MembershipWrapper : IMembershipWrapper
{
public bool ValidateUser(string userName, string password)
{
return Membership.ValidateUser(userName, password);
}
}

and

1
2
3
4
5
6
7
public class FormsAuthenticationWrapper : IFormsAuthenticationWrapper
{
public void SetAuthCookie(string userName, bool rememberMe)
{
FormsAuthentication.SetAuthCookie(userName, rememberMe);
}
}

And a couple interfaces

1
2
3
4
public interface IMembershipWrapper
{
bool ValidateUser(string userName, string password);
}

And

1
2
3
4
public interface IFormsAuthenticationWrapper
{
void SetAuthCookie(string userName, bool rememberMe);
}

Not exactly rocket science.

Once I finished that, I also had to handle the Controller.Url property. I used a technique I found at @nvthoai’s site.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public interface IUrlHelperWrapper
{
string Action(string actionName, string controllerName);
 
string Action(string actionName, string controllerName, object routeValues);
 
string Action(string actionName, string controllerName, RouteValueDictionary routeValues);
 
bool IsLocalUrl(string url);
}
 
public class UrlHelperWrapper : UrlHelper, IUrlHelperWrapper
{
internal UrlHelperWrapper(RequestContext requestContext)
: base(requestContext)
{
}
 
internal UrlHelperWrapper(RequestContext requestContext, RouteCollection routeCollection)
: base(requestContext, routeCollection)
{
}
 
public UrlHelperWrapper(UrlHelper helper)
: base(helper.RequestContext, helper.RouteCollection)
{
}
}

Then I had to create class member variables in the controller

1
2
3
private IMembershipWrapper membership;
private new IUrlHelperWrapper Url;
private IFormsAuthenticationWrapper authentication;

And new constructors

1
2
3
4
5
6
7
8
9
10
11
12
13
public AccountController()
:this(new MembershipWrapper(),new FormsAuthenticationWrapper(), null)
{
}
 
public AccountController(IMembershipWrapper membershipObject,
IFormsAuthenticationWrapper formsAuthenticationObject,
IUrlHelperWrapper urlHelper)
{
this.membership = membershipObject;
this.Url = urlHelper;
this.authentication = formsAuthenticationObject;
}

Then override the Initialize method in order to setup my UrlHelperWrapper.

1
2
3
4
5
6
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
if(Url == null)
Url = new UrlHelperWrapper(requestContext);
}

And finally, I changed the LogOn method to use the new wrappers instead of the static methods.

1
2
3
4
5
...
if (membership.ValidateUser(model.UserName, model.Password))
{
authentication.SetAuthCookie(model.UserName, model.RememberMe);
...

Ok, yeah; nothing in there is rocket science. I admit it, and when you know what to do, it’s not difficult. However, a few things become painfully obvious when you do this:

1. 100% code coverage not only isn’t practical, it’s not even possible since the wrappers cannot be tested.

2. You’ll have to create a new wrapper for every static method you ever use. I suppose you could use one wrapper for all static methods, but I prefer to keep the class names lined up.

3. I need to pass a wrapper instance into the constructor for every wrapper my testing *class* contains, as opposed to the *Action* I’m testing.

4.I have to design my code in a specific way (a way which I would NEVER do on my own) in order to make it testable.

So my test winds up looking like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[TestMethod]
public void Logon_ValidReturnUrl_ReturnsRedirectToUrl()
{
// Arrange
Mock membership = new Mock();
membership.Setup(m => m.ValidateUser(It.IsAny(), It.IsAny())).Returns(true);
Mock authenticator = new Mock();
Mock urlHelper = new Mock();
urlHelper.Setup(h => h.IsLocalUrl(It.IsAny())).Returns(true);
var controller = new AccountController(membership.Object, authenticator.Object, urlHelper.Object);
var model = new LogOnModel();
const string testUrl = "/something/inteREsting/2/ensure/test/returns/correct/url/";
 
// Act
ActionResult result = controller.LogOn(model, testUrl);
 
// Assert
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof(RedirectResult));
var redirectResult = result as RedirectResult;
Assert.AreEqual(testUrl, redirectResult.Url);
}

Now when I look at that test, and especially as the number of controller constructor parameters starts growing, and I add more tests with similar ‘arrange’ sections, I feel compelled to start cleaning and organizing my test code. I suddenly want to create test class member variables for each of the mocks required by the constructor, and add other overhead … I’m unsatisfied with the above code and really, really, want to clean it up.

So after spending a weekend writing that crap instead of adding features, I was sufficiently frustrated, and spent my drive to work Monday morning thinking about it. Mostly, I wondered long and hard about if tests are considered ‘waste’ from a Lean Startup point of view. And to be honest, I was really leaning toward … ‘yes: they’re waste’.

Then when I got to work, I was tasked with the challenge of how to unit test a webforms application, which is really a question of ‘How can we mock it?’ This search led me to Ivonna, which looked great but relies on TypeMock Isolator.

Now I’ve heard of Typemock before, I’ve actually followed them on Twitter for at least a year, but never really looked into it. To make a long story short, I was a little surprised by the price, and really couldn’t understand why anybody would pay when there are so many free mocking tools.

That’s when I found out I can mock static methods … which as you can imagine, after that weekend, I was intrigued to put it mildly …. actually ‘converted to fanboy’ status is probably a little more accurate. 😉

Here’s the same test on the original method using Typemock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[TestMethod, Isolated]
public void Logon_ValidReturnUrl_ReturnsRedirectToUrl()
{
// Arrange
var controller = new AccountController();
Isolate.WhenCalled(() => Membership.ValidateUser(string.Empty, string.Empty)).WillReturn(true);
Isolate.WhenCalled(() => FormsAuthentication.SetAuthCookie(string.Empty, false)).IgnoreCall();
Isolate.WhenCalled(() => controller.Url.IsLocalUrl(string.Empty)).WillReturn(true);
var model = Isolate.Fake.Instance();
const string testUrl = "/something/inteREsting/2/ensure/test/returns/correct/url/";
 
// Act
ActionResult result = controller.LogOn(model, testUrl);
 
// Assert
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof(RedirectResult));
var redirectResult = result as RedirectResult;
Assert.AreEqual(testUrl, redirectResult.Url);
}

When you compare this test with the test using Moq, the differences are trivial, but I don’t feel compelled to ‘clean it’, and … get this …. I didn’t have to change anything in my app.

Let me repeat that, I didn’t have to change anything in my app. I didn’t have to write any wrapper classes, modify my constructors, pollute my class with unnecessary member variables, or spend 45 mintues trying to figure out how to mock the Controller.Url property, or put part of my wrapper initialization code into the Initialize method because I couldn’t initialize one of my wrappers in the constructor.

I mean all those changes just felt like a wretched code smell.

But the Typemock test was all straight forward. I felt like I sprayed my code with deodorizer.

Maybe I was using Moq the wrong way, but based on my research on StackOverflow, it seems that wrappers are the way everybody is managing this. If you know a better way, please let me know in the comments.

PS-Last week I sat in on a Typemock seminar, and the guy doing the seminar, Gil Zilberfeld, said something which really resonated with me, he said

“If you’re doing things just for testing, you’re doing it wrong.”

…. it’s as if he was talking directly to me.

5 Responses to “Discovering Typemock”

  1. Yakup says:

    I had been hear that TypeMock use few tricks at pre-compile time for achieve these. Also i know PostSharp also use such tricks and let you achieve hard things very easily and nicely.PostSharp.

    I could never be accept to write such a test for such a controller method that require bunch of unneccesary interfaces and wrappers that makes the things more harder.

    I totally agree “If you’re doing things just for testing, you’re doing it wrong.”.

    I don’t know price of it but wish it be free.

    • John MacIntyre says:

      While I was disappointed to see a price on Typemock, after using OSS for this type of thing, I do understand the realities of needing revenue to maintain a sustainable company. And really, when you consider the amount of developer time a company can save with it, I don’t think the price is unreasonable.

    • Idan says:

      If you’re a consultant or a student, we have special pricing. Feeel free to contact me at idan (at) typemock.com

  2. Gil Zilberfeld says:

    You’re welcome!

Leave a Reply to John MacIntyre Cancel reply

Your email address will not be published. Required fields are marked *

top