Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Isolating Dependencies in Tests Using Mocks and Stubs : Page 4

Unit tests are not "unit" tests if they test things other than the System Under Test (SUT).


Using Dynamic Fake Objects

There are several frameworks out there (often referred to as "mock frameworks"), both commercial and free, that will create dynamic fake objects for you. The more popular free ones are Rhino Mocks and MOQ, while TypeMock is a popular commercial version. While all the frameworks provide similar kinds of features, they're implemented differently. I'll use Rhino Mocks in this article to rewrite the tests you've seen so far. You can go ahead and get rid of both the StubView and StubRepository classes.

Go back to the VideosListPresentationModel_InitialStates_Tests test class and make a few changes to it. That class originally had fields to store references to your stub view and repository objects:

private static StubView View; private static StubRepository Repository;

Because you don't have those fake classes any more, you need to change the types of those fields to their respective interface types:

private static IVideosListView View; private static IVideoRepository Repository;

Next, you need to change the code where you were assigning instances of those fake classes to those fields. Use Rhino Mock's MockRepository's GenerateStub method to create dynamic mocks for those interfaces. The MockRepository class exists under the Rhino.Mocks namespace:

[ClassInitialize()] public static void FixtureSetup(TestContext tc) { View = MockRepository.GenerateStub<IVideosListView>(); Repository = MockRepository.GenerateStub<IVideoRepository>(); PM = new VideosListPresentationModel( View, Repository); }

Rhino creates a dynamic class at run time that provides a minimal implementation (very similar to the classes you had created before, except that you don't need to maintain individual classes yourself).

Those are the only changes you need to incorporate Rhino Mocks into the tests. You should be able to run the tests and see they all pass.

Now off to the other three tests you wrote; the ones testing the retrieval behavior for the list of videos. Remember, you needed to add more code to your fake classes to support those tests. First, you had to add a method to the StubView class so you could raise the VideoListRequested event on the view:

public void RaiseVideoListRequestedEvent( string filter) { VideoListRequested(this, new VideoListRequestedEventArgs(filter)); }

You also added a SetFakeData method to the StubRepository class so you could feed it some test data:

private List<Video> m_ListOfVideos; public void SetFakeData(List<Video> listOfVideos) { m_ListOfVideos = listOfVideos; }

Finally, you had to implement methods on the StubRepository class to retrieve the lists of videos:

public IEnumerable<Video> GetAllVideos() { return m_ListOfVideos; } public IEnumerable<Video> GetAllVideosOfType( VideoType videoTypeFilter) { return m_ListOfVideos.Where( v => v.VideoType == videoTypeFilter); }

You can now rewrite these tests, leveraging the possibilities Rhino Mocks gives you.

As you've done before, get rid of the references to both StubView and StubRepository, and replace those with references to their respective interfaces. You use the MockRepository.GenerateStub<interface>() method as you did before. Nothing new so far.

You can remove the call to SetFakeData, because you don't have an object with that method anymore:

[ClassInitialize()] public static void FixtureSetup(TestContext tc) { // unchanged code elided // This is no longer necessary! // Repository.SetFakeData(m_ListOfVideos); }

The Should_set_current_filter_to_requested_filter test requires a simple change; instead of calling a RaiseVideoListRequestedEvent method, you raise the event by using a feature of Rhino Mocks that allows you to raise events on an object:

[TestMethod]public void Should_set_current_filter_to_requested_filter() { string filter = VideoType.Dvd.ToString(); Assert.AreNotEqual(filter, PM.CurrentVideoTypeFilter); View.GetEventRaiser(x => x.VideoListRequested += null) .Raise(null, new VideoListRequestedEventArgs(filter)); Assert.AreEqual(filter, PM.CurrentVideoTypeFilter); }

By importing the Rhino.Mocks namespace to the current source file, you get several extension methods, free of charge, that help you when writing your tests. One is the GetEventRaiser method. That method as an argument a lambda expression containing the expression used to wire up an event:

x => x.VideoListRequested += null

You assign null to the event because you don't really need to handle that event here. GetEventRaiser returns an IEventRaiser object, which in turn has a Raise method. You then call Raise, passing in whatever parameters are necessary when the event is raised. In this case, the VideoListRequested event expects a sender object, as well as a VideoListRequestedEventArgs object:

.Raise(null, new VideoListRequestedEventArgs(filter));

You're passing null as the sender because that parameter is irrelevant in this case.

Off to the Should_retrieve_all_videos_if_no_filter_spec (specified) test method. That method raises the VideoListRequested event, passing in "(No Filter)" as the filter, and then checks whether you get the correct number of videos back. The only change you need here is to use GetEventRaiser/Raise to raise the event, just as in the previous test.

The test also needs to provide the repository with some test data, because you no longer have that SetFakeData method, and the dynamic stub doesn't have any data by default. Under this scenario (the user requests a list with no filter applied), you know that the presentation model is going to call a GetAllVideos method on the repository. So you need to "stub out" that method call, making it return whatever you need to satisfy the expected context of your test. The dynamic stub gives you a Stub method that allows you to do just that:

[TestMethod]public void Should_retrieve_all_videos_if_no_filter_spec() { Repository.Stub(x => x.GetAllVideos()) .Return(m_ListOfVideos); // remaining code elided }

The call to the Stub method accepts a lambda expression containing your expected call to GetAllVideos, and the call to the Return method takes the object you want returned whenever that call to GetAllVideos happens. In other words, you could read that line of execution as: "Whenever GetAllVideos is called on the repository, return m_ListOfVideos."

You're done with that test, so move on to the other one: Should_retrieve_only_videos_for_specified_filter. That test raised the VideoListRequested event, passing in a specific filter (VideoType.BluRay). The changes you have to make to this test are similar to the changes you've made to the previous one: use GetEventRaiser/Raise to raise the event, and before that, stub out the call to the method on the repository that you expect the presentation model to make (in this case, GetAllVideosOfType), and return the expected test data:

VideoType filter = VideoType.BluRay; IEnumerable<Video> filteredList = m_ListOfVideos.Where(v => v.VideoType == filter); Repository.Stub(x => x.GetAllVideosOfType(filter)) .Return(filteredList); View.GetEventRaiser(x => x.VideoListRequested += null) .Raise(null, new VideoListRequestedEventArgs( filter.ToString()));

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date