I went to the CMAP Code Camp a few weeks ago, and sat in a session about mocking. I wasn't new to mocking by any means, but I thought it would be useful to listen to other thoughts/opinions. Code Camp sessions, by the way, are a great way to improve your skills. Even if you're not new to a concept, that doesn't mean you're not going to get anything out of it. Code Camp sessions are not like college classes. The speakers don't just feed you information and tell you to shut-up. The entire environment promotes discussion. Had a problem with something? Would like a second opinion on an idea? Speak up.
Anyway, the speaker (Scott Allen) walked through a few real-world examples. These examples included some very basic unit tests using xUnit.net. After a while, though, he started mocking a few services with Rhino Mocks. Having used Rhino Mocks previously, I knew what was going on. Right before the end of the session, he took one test and coded it using Moq, another mocking framework that utilizes some of the new C# 3.0 syntax sugar. Before this session I never really took a serious look at Moq, or even xUnit.net. Today, I decided to try both.
The New'ish Guy - xUnit.net
Another unit testing framework? Yes, but it's not the same thing packaged up with a new name. xUnit.net has added some new things (Assert Exceptions without Attributes), modified some things (Fact attribute, instead of Test) and removed some things (no TestFixture attribute, no SetUp or TearDown attributes).
I know, I know. Why be different? Why have a Fact, instead of a Test? It's interesting because I felt the same way. Everyone uses the Test attribute, why make it Fact now? After using it for a while, it just makes sense. From my standpoint it just promotes better test names. I feel some people create test names that don't fully tell the whole story. Fact makes me want to type out a name that should represent a fact. For example, I will be posting some code that will test a method to make sure that, if a domain entity has not been found, will redirect the user. Some people may feel inclined to write "TestRedirect". But you're not testing a redirect really. We're using a framework that we're not responsible for so the redirect code should work. What we're trying to test is the interaction between the domain and the controller. When my domain entity is null, I want to call code that redirects the user to a certain page. Wouldn't a test named "View_Action_Should_Redirect_User_To_Index_If_Ballpark_Is_Not_Found" or something like that make more sense? That's a fact.
A Little About Moq
Moq is a mocking framework that takes advantage of some new language features and helps reduce lines of code in certain areas. It takes certain aspects like Record and Playback, and just hides them behind the scenes. Moq is actually pretty cool. I wouldn't call it completely mind blowing, but it's pretty neat. The ability to use new language features and it's own features to make your tests less verbose is a good thing.
The Example
I'm going to use the scenario I mentioned above. I have a ballpark controller. In it I have an action called "View" that gets a ballpark based on the id passed to it. If the ballpark is found, I want it to display the ballpark details using the "viewballpark" view. However, if my ballpark is not found, I just want to redirect the user to the index action in the same controller. Obviously, this may not be what you want to do. You may want to display an error page. For this example, though, this is what we want to do. I'm not going to use the TDD approach for this example as I have my controller action already coded. I'm going to show you the controller action, a failing test, and then a test that passes.
First, the controller action:
public ActionResult View(int id)
{
Ballpark park = ballparkService.GetBallpark(id);
if (park == null)
return RedirectToAction("index");
BallparkControllerViewData viewData = new BallparkControllerViewData {Ballpark = park};
return RenderView("view", viewData);
}
In my code above, I'm calling my ballpark service to get the ballpark based on the id and then returning what it found. If it's null, I'm redirecting the user to the index action. If it's not null, I'm setting up my viewdata and then displaying the view. Pretty simple.
Now, here's a test that will fail:
[Fact]
public void Unknown_Ballpark_Should_Redirect_User_To_Index()
{
var ballparkServiceMock = new Mock<IBallparkService>();
ballparkServiceMock.Expect(x => x.GetBallpark(It.IsAny<int>())).Returns(new Ballpark());
BallparkController controller = new BallparkController(ballparkServiceMock.Object);
var result = controller.View(1) as ActionRedirectResult;
result.ShouldNotBeNull();
result.Values["action"].ShouldEqual("index");
ballparkServiceMock.VerifyAll();
}
First, what is going on? Using Moq, we're creating a mocked instance of our ballpark service in the first line of the method.
Why are we mocking this? Since our ballpark service will eventually communicate with some data source, we don't want to have our controller unit tests depend on an external data source. For one, this will increase the testing time. If we have to wait for the database every time we want to run our unit tests, this will dramatically increase the amount of time it takes the runner to step through the tests. Second, it's not very dependable. If we created this test on Monday and used ID 45 because there was no such ballpark with an ID of 45 at the time, the test would work. However, if another developer creates a new ballpark that has an ID of 45 later in the week, the test will now fail. Of course, we could always clean up the data before we execute the test, but that leads me to my next point. Finally, we just don't care. We're testing the controller and how it works, not if the database is working. If we test too many things at once, our tests become fragile and brittle. We want to make sure the controller lives up to it's expectations. In order to accomplish this, we want to control what data is returned. For this reason, we're going to mock our service.
In the second line, we're setting our expectations. We're saying to the mocking framework: For this mock, we have a method called GetBallpark. We expect it to be called. We don't care what integer it passes in (It.IsAny<int>()). We just want to know if it was called. And if it is called, we want you to return what we tell you. In our expectation, we're returning a new instance of a ballpark. When the controller calls the service's GetBallpark method, it will return the new instance.
Now, we're done setting up our expectations. The action is pretty dumb so we don't expect much from it. Next, we're going to call our action, pass in some random integer and get the result. Since the code should redirect the user, we should expect it to return an instance of ActionRedirectResult. Since we're casting using the as keyword, it will just set the result variable to null if it cannot cast it.
All we have to do now is run our assertions to see if what we expected to happen actually happened.
In this example our test failed. But why? Well, we expected our controller to redirect the user to an action since the ballpark was null. But wait, our ballpark wasn't null. The ballpark service actually returned an instance of a ballpark because we told it to. Because of this, an ActionRedirectResult was not returned. Instead, a RenderViewResult was returned which caused our cast to fail. Our result variable is now null, which caused the first assert to fail (result.ShouldNotBeNull()).
Fixing this should be pretty easy; we just need to remove the return value in our expectations.
[Fact]
public void Unknown_Ballpark_Should_Redirect_User_To_Index()
{
var ballparkServiceMock = new Mock<IBallparkService>();
ballparkServiceMock.Expect(x => x.GetBallpark(It.IsAny<int>()));
BallparkController controller = new BallparkController(ballparkServiceMock.Object);
var result = controller.View(1) as ActionRedirectResult;
result.ShouldNotBeNull();
result.Values["action"].ShouldEqual("index");
ballparkServiceMock.VerifyAll();
}
Now, that test works. All we did was remove the return specification from the expect call. Why did that work?
Mocks - Loose and Strict
By default, when you create a mock without specifying a Moq "mockbehavior", it will create a loose mock, instead of a strict mock. A loose mock can be your friend, but it also can be your enemy. When you use a loose mock, the mocking framework will allow calls to everything, even if you didn't tell the mock to expect it or give it a return value. Since you didn't give it a return value, Moq (and Rhino Mocks) will just return that type's default value. In this case, it will return null for the method. In this example, that's good for us since our test wants GetBallpark to return null. If we had a strict mock, on the other hand, our test would fail because we didn't setup the return result.
The Rest of the Assertions
Since our last test code worked, it went through all of the assertions and passed. Our result variable wasn't null anymore, since our controller did redirect. Our result values collection contained a key called "action" with the value "index" as it redirected the user to the index action. And finally we verified that all mocking expectations have been met. In this case, they have.
xUnit.net Extension Methods
You may have noticed that our assertions didn't actually say "Assert" this and "Assert" that. Instead, our assertion methods were called from the variable we were asserting. xUnit.net has several extension methods that created this behavior. In order to add these methods, you must reference the xunit.net 3.5 extension method DLL that's located in the package.
Closing Up
I'll post a few more mocking examples shortly. I'll also post examples on MonoRail controller mocking.
Currently rated 5.0 by 1 people
- Currently 5/5 Stars.
- 1
- 2
- 3
- 4
- 5