Unit Testing using NUnit and Rhino Mocks
Unit Testing
The smallest piece of a software that can be testable is called as a Unit. In Object Oriented Programming languages like C# each class is called as a Unit. Testing each such unit independent of others is called as Unit-Testing. Unit Testing is very important in Test Driven Development (TDD) where the development starts with writing test cases before code. Lot of testing frameworks are available for doing better unit testing in different languages. For .NET there are frameworks like NUnit, TestDriven.NET, xUnit.net and more.
Usually a class has dependencies with other classes. Unit Testing is all about testing the functionalities of the class in test and not its dependencies. In order of using the real instances of the dependencies we can use dummy or mock objects. NUnit itself comes with mocking support but there are plenty of other frameworks like Rhino Mocks, NMock, TypeMock.NET etc. that does the job better than NUnit. Some of them are open-sources and some are paid. I usually go with Rhino Mocks, it’s an open source and easy to use.
In this article we are going to see about how to do better unit testing in .NET using NUnit and Rhino Mocks.
NUnit
NUnit is an open source unit testing framework written completely in C#. It is initially ported from the unit testing framework written for Java called jUnit. NUnit run the tests in a separate process called nunit-agent.exe. By attaching to this process from Visual Studio we can even debug our test cases. We can run test cases from multiple assemblies by creating project files; these are xml files having extension as .nunit which contains information about the assemblies.
GUI Runner
NUnit comes with three types of runners for running test cases: Console, GUI and pUnit. I use GUI runner for running tests and one nice thing I noticed down is it remembers state i.e. you don’t need to load the assemblies every time you fire up the runner. Once you loaded the assemblies for the first time, then from the next time it preloads the assemblies. Not only the assemblies it even remembers other things like the last test cases that were ran etc. One more interesting thing is whenever you make changes to the test assembly like adding new methods the runner will automatically reflect the changes without manually reloading the assembly.
In the left-pane all the test classes and methods in the assemblies are displayed and in the right-pane the test results are displayed. The runner contains two buttons for running and stopping test cases. The runner also displays the no. of tests passed, failed, ignored, time taken etc.
TestFixture and Test Attributes
The two most important attributes of NUnit are TestFixture and Test. We use these two attributes for every test class we write. TestFixture attribute is used to mark a class as a test class and Test attribute is used to mark methods in a test class as test methods. It is not mandatory that all the classes in the test assembly have to be marked with TestFixture attribute and all the methods in a test class has to be marked with Test attribute. NUnit only display the classes and their methods that are decorated with those attributes in the GUI runner.
Let’s say we have a simple class FileManager with a constructor and a method named GetMetaData. The GetMetaData method returns information about a file like Name, Created Date, File Size etc. as a custom object MetaData.
public class MetaData { public string Name { get; set; } public string FullName { get; set; } public DateTime CreatedOn { get; set; } public long Size { get; set; } } public class FileManager { private string file; public FileManager(string file) { if (string.IsNullOrEmpty(file)) throw new ArgumentException("File should not be null or empty."); if (!File.Exists(file)) throw new FileNotFoundException(String.Format("File {0} not exists", file)); this.file = file; } public MetaData GetMetaData() { var fileInfo = new FileInfo(file); return new MetaData { Name = fileInfo.Name, FullName = fileInfo.FullName, CreatedOn = fileInfo.CreationTime, Size = fileInfo.Length }; } }
Listing 1. Sample class to test
We have to write test cases for both the constructor and GetMetaData method. Let’s create a test assembly with a test class FileManagerTests that will contain all the tests related to the FileManager class.
[TestFixture] public class FileManagerTests { }
Listing 2. Test class
First start testing the constructor. The constructor takes a file (with full path) as a parameter and check for null or empty and also the file really exists in the hard drive or not. If any of the condition fails exceptions are thrown from the constructor. We are going to test the conditions works as expected for different inputs.
Let write a test case by passing an empty value to the constructor and see whether the ArgumentException is getting thrown, if not then there is something wrong.
[TestFixture] public class FileManagerTests { [Test(Description = "Empty File Test")] public void Constructor_EmptyFileTest() { bool isArgumentExceptionThrown = false; try { var fileManager = new FileManager(String.Empty); } catch (ArgumentException) { isArgumentExceptionThrown = true; } Assert.AreEqual(true, isArgumentExceptionThrown); } }
Listing 3. A test to verify whether ArgumentException is thrown on passing empty file path
Let see what we have done. We’ve created a method named Constructor_EmptyFileTest which is marked with Test attribute. All the test methods have to be public. The Test attribute contains a property Description that says what is the test about. We are passing an empty string to the FileManager constructor and checking whether ArgumentException is thrown using the NUnit's Assert statement. NUnit has lot of Assert methods and they are basically used to check whether the actual and expected results are same.
Some of the frequently used Assert methods are:
Method | Description |
---|---|
Assert.AreEqual |
Verifies the two objects are equal. Ex. Assert.AreEqual(expected, actual) Assert.AreEqual(expected, actual, "The expected and actual are not equal.") If two objects are not equal then the text passed at last is displayed in the runner. |
Assert.AreNotEqual |
Verifies that two objects are not equal. Ex. Assert.AreNotEqual(expected, actual) Assert.AreNotEqual(expected, actual, "The expected and actual are equal.") |
Assert.IsNull |
Verifies the passed object is null. Ex. Assert.IsNull(actual) Assert.IsNull(actual, "The actual is not null.") |
Assert.IsNotNull |
Verifies the passed object is not null. Ex. Assert.IsNotNull(actual) Assert.IsNotNull(actual, "The actual is null.") |
Assert.IsEmpty |
Verifies the passed string is empty. Ex. Assert.IsEmpty(actual) Assert.IsEmpty(actual, "The passed string is not empty.") |
Assert.IsTrue |
Verifies the passed condition is true or not. Ex. Assert.IsTrue(actual) Assert.IsTrue(actual, "The passed condition is not true.") |
Assert.IsFalse |
Verifies the passed condition is false or not. Ex. Assert.IsFalse(actual) Assert.IsFalse(actual, "The passed string is not false.") |
Assert.IsInstanceOf |
Verifies the passed object is of the particular type. Ex. Assert.IsInstanceOf(typeof(Employee), actual) Assert.IsInstanceOf(typeof(Employee), actual, "The object is of not type Employee.") |
In the above test method we have used try-catch block to check whether the expected exception is thrown or not but NUnit comes with an attribute ExpectedException that simplifies our job as shown below.
[Test(Description = "Empty File Test")] [ExpectedException(typeof(ArgumentException))] public void Constructor_EmptyFileTest() { var fileManager = new FileManager(String.Empty); }
Listing 4. ExpectedException attribute
Now for running our test methods we have to load the test assembly in the GUI runner and click the Run button. If the test passes successfully we will see a green else red strip bar at the right pane.
Let write three more test cases passing null, invalid and valid file parameters to the constructor.
[Test(Description = "Null Test")] [ExpectedException(typeof(ArgumentException))] public void Constructor_NullTest() { var fileManager = new FileManager(null); } [Test(Description = "Invalid File Test")] [ExpectedException(typeof(FileNotFoundException))] public void Constructor_InvalidFileTest() { var fileManager = new FileManager(@"D:\Documents\not_exists.doc"); } [Test(Description = "Constructor Positive Test")] public void Constructor_PositiveTest() { var fileManager = new FileManager(@"D:\Documents\Vijaya_Anand.doc"); }
Listing 5. More unit tests
All the above four test cases are related to testing constructor and they all vary only in the passed parameter. NUnit comes with a very nice attribute TestCase which is alternative to the Test. Unlike Test attribute we can add the TestCase attribute more than once over a method. Below is a single method that combines all our above four test cases.
[TestCase("", Description = "Empty File Test", ExpectedException = typeof(ArgumentException))] [TestCase(null, Description = "Null Test", ExpectedException = typeof(ArgumentException))] [TestCase(@"D:\Documents\not_exists.doc", Description = "Invalid File Test", ExpectedException = typeof(FileNotFoundException))] [TestCase(@"D:\Documents\Vijaya_Anand.doc", Description = "Constructor Positive Test")] public void ConstructorTest(string file) { var fileManager = new FileManager(file); }
Listing 6. TestCase attribute
The ConstructorTest method takes the input parameter file and each TestCase attribute passes a different value for that. Although the method is single there are four test cases associated with that.
We have completed writing the test cases for constructor and below is the test case for GetMetaData method.
[Test(Description = "GetMetaData method test")] public void GetMetaDataTest() { var file = @"D:\Documents\Vijaya_Anand.doc"; var fileManager = new FileManager(file); var expected = new FileInfo(file); var actual = fileManager.GetMetaData(); Assert.IsNotNull(actual, "The returned metadata instance is null."); Assert.AreEqual(expected.Name, actual.Name, "Name is not same."); Assert.AreEqual(expected.FullName, actual.FullName, "Path is not same."); Assert.AreEqual(expected.CreationTime, actual.CreatedOn, "Created time is not same."); Assert.AreEqual(expected.Length, actual.Size, "Size is not same."); }
Listing 7. Unit test for GetMetaData method
In the above method we are testing the returned MetaData object by the FileManager contains values same as expected.
SetUp and TearDown Attributes
The SetUp attribute is used when we have to do some initial setup before running each test case. The TearDown attribute does the opposite, for cleaning up resources and other things after running each test.
[SetUp] public void Setup() { // this method will be called before running every test } [TearDown] public void Cleanup() { // this method will be called after running every test }
Listing 8. Setup and TearDown attributes
Ignoring Tests
It’s normal in project development life-cycle some test cases become no longer valid due to requirement changes or some other reasons. To avoid running those tests we can either remove the complete test case or remove the attribute from those methods. But they are not good approaches instead of that we can mark the invalid test methods with Ignore attribute. We can also specify why the test case is ignored by passing a reason to the constructor of the Ignore attribute. Although those tests are displayed in the console they are ignored by the runner.
[Ignore("Requirement changed - 7/7/2011")] [Test] public void InvalidTest() { // this test method no longer valid }
Listing 9. Ignore attribute
When we are using TestCase attributes like in our above ConstructorTest then marking the method with Ignore attribute ignores all the test cases from running. Suppose we want to ignore only one or two test cases then we have to go for other option. The TestCase attribute itself comes with couple of properties Ignore and IgnoreReason. Ignore is a boolean property when we set it as true then the particular TestCase will be ignored.
Running tests based on platform
NUnit also supports running or excluding test cases based upon the platform using the Platform attribute. Here the platform means a particular version of .NET or OS.
[Platform("Win32")] [Test] public void Win32Test() { // this test will run only in Win32 machines } [Platform(Exclude = "Net-1.0,Net-1.1,Net-2.0")] [Test] public void NewTest() { // this test won’t run in .NET older frameworks 1.0, 1.1 and 2.0 }
Listing 10. Platform attribute
Categorizing test cases
One nice feature I like with NUnit is categorizing tests. In our FileManager example say we have added a new method named SetMetaData. This method sets the creation time for a file.
public void SetMetaData(MetaData metaData) { if (metaData.CreatedOn > DateTime.Now) throw new Exception("Created date should not be a future date."); File.SetCreationTime(file, metaData.CreatedOn); }
Listing 11. SetMetaData method
Suppose I want to categorize my tests of FileManager based on iterations. All the methods except SetMetaData are part of Iteration 1 and it is part of Iteration 2. So at any point of time I can select a particular iteration and run its tests. NUnit provides a nice attribute called Category to achieve that.
Below is the test case newly added to the FileManagerTests for testing SetMetaData. You can notice we have marked the test method with Category attribute passing "Iteration2" in the constrcutor.
[Category("Iteration2")] [Test(Description = "SetMetaData method test")] public void SetMetaDataTest() { var file = @"D:\Documents\Vijaya_Anand.doc"; var fileManager = new FileManager(file); var expected = DateTime.Now.AddDays(-3); fileManager.SetMetaData(new MetaData { CreatedOn = expected }); var actual = File.GetCreationTime(file); Assert.AreEqual(expected, actual); }
Listing 12. Category attribute
In NUnit GUI runner the left pane contain two tabs "tests" and "categories". The categories tab contains all the categories. To run all the tests of a particular category we have to select a particular category and click the Add button at the bottom.
Running tests in different threads
NUnit run all the tests in the assemblies in a same thread. Suppose we want to run a test in a different thread NUnit supports that option using RequiresThread attribute.
Setting timeout for tests
Sometimes we want to make sure a test doesn’t exceed more than a particular time. NUnit provides an attribute called Maxtime for that.
[MaxTime(5000)] public void TestThatShouldNotExceedFiveSeconds { // test will pass only if it completes within 5 seconds }
Listing 13. MaxTime attribute
So far we have discussed about how to write and run test cases in NUnit. We have also seen about some interesting attributes from NUnit. Now let see how we can mock dependency objects using Rhino Mocks to do effective unit-testing.
Rhino Mocks
As I already said that unit testing is all about testing the functionalities of the class under test and not its dependencies. Sometimes setting up such dependency classes is difficult and delays the unit testing. The dependency classes that usually create difficulties are database components, web services, logging components etc. Instead of using the real instances of these dependencies it would very nice to replace them with mock objects. Rhino Mocks helps to create mock objects by two ways: either from an interface or a class with methods marked as virtual. I prefer to use interfaces and create mock objects for them instead of marking the real class methods as virtual.
So when we create components the dependency between the classes has to be loosely coupled by interfaces for better unit testing else it would be painful. There are some powerful mocking frameworks like TypeMock.NET that even mock concrete classes through interception. But I recommend using interfaces whenever there is a dependency with heavyweight classes like database classes, web service classes etc.
Suppose we have a class named MovieManager that consumes a third-party web service which returns a list of famous Hollywood movies. The MovieManager class has a method named GetMoviesOrderedByRate that returns the movies ordered by their rate.
public class Movie { public string Name { get; set; } public int Year { get; set; } public string DirectedBy { get; set; } public string Actors { get; set; } public string Rate { get; set; } // ex. "4.8/5" } public class MovieManager { public Movie[] GetMoviesOrderedByRate() { MovieServiceClient client = null; try { client = new MovieServiceClient(); var movies = client.GetMovies(); var moviesByRate = from m in movies orderby m.Rate.Split('/')[0] descending select m; return moviesByRate.ToArray(); } finally { if (client != null) client.Close(); } } }
Listing 14. MovieManager class
Let see how we can write test cases for the GetMoviesOrderedByRate method of the MovieManager class. If you see the MovieManager class it is tightly coupled with the web service proxy class MovieServiceClient. Now for testing this MovieManager class we want to specify the web service configuration in a config file also the testing will take more time because of the web service calls. Our aim is not to test whether the web service is returning the movies but the method sorts the movies returned by the service in the descending order of Rate. It would be nice if we pass a dummy mock object in the place of the web proxy class that will return some hard-coded movies and to do that we have to modify the above class little.
public class MovieManager : IDisposable { IMovieService movieService; public MovieManager(IMovieService movieService) { this.movieService = movieService; } public MovieManager() { this.movieService = new MovieServiceClient(); } public Movie[] GetMoviesByRate() { var movies = movieService.GetMovies(); var moviesByRate = from m in movies orderby m.Rate.Split('/')[0] descending select m; return moviesByRate.ToArray(); } public void Dispose() { if (movieService != null) { var proxy = movieService as IClientChannel; if (proxy != null) { if (proxy.State != CommunicationState.Closed) proxy.Close(); proxy.Dispose(); } } } }
Listing 15. Refactored MovieManager for better unit testing
We have changed the class to use interface which is the service contract IMovieService and also used overloaded constructors. Now we can pass a mock implementation for IMovieService to the MovieManager while testing.
In Rhino Mocks creating a mock implementation for IMovieService can be easily done by using the class MockRespository.
var movieServiceMock = MockRepository.GenerateMock<IMovieService>();
Listing 16. Creating mock for IMovieService
Now we can easily pass this mock instance to the MovieManager using the overloaded constructor.
var movieManager = new MovieManager(movieServiceMock);
Listing 17. Passing IMovieService mock to MovieManager
That’s not enough in our example. When the MovieManager calls method GetMovies our mock instance returns null and we will get a ArgumentNullEXception. So we have to make sure when the MovieManager calls the method we have to return some movies back and that can be done using the extension method Stub from Rhino Mocks.
var movies = new Movie[3]; // fill movies array movieServiceMock.Stub(x => x.GetMovies()).IgnoreArguments().Return(movies);
Listing 18. Creating stub methods
The Stub method is used to create method stubs for mock objects. And finally here is our test method.
[TestFixture] public class MovieManagerTests { [Test(Description="Testing GetMoviesByRate method")] public void GetMoviesByRateTest() { var movieServiceMock = MockRepository.GenerateMock<IMovieService>(); var movies = new Movie[3]; var troy = new Movie { Name = "Troy", Year = 2004, DirectedBy = "Wolfgang Petersen", Actors = "Brad Pitt, Eric Bana, Orlando Bloom", Rate = "4.5/5" }; var titanic = new Movie { Name = "Titanic", Year = 1997, DirectedBy = "James Cameron", Actors = "Leonardo DiCaprio, Kate Winslet", Rate = "4.8/5" }; var gladiator = new Movie { Name = "Gladiator", Year = 2000, DirectedBy = "Ridley Scott", Actors = "Russell Crowe, Joaquin Phoenix", Rate = "4.7/5" }; movies[0] = troy; movies[1] = titanic; movies[2] = gladiator; movieServiceMock.Stub(x => x.GetMovies()) .IgnoreArguments() .Return(movies); var movieManager = new MovieManager(movieServiceMock); var actual = movieManager.GetMoviesByRate(); movieManager.Dispose(); Assert.AreEqual(titanic.Name, actual[0].Name); Assert.AreEqual(gladiator.Name, actual[1].Name); Assert.AreEqual(troy.Name, actual[2].Name); } }
Listing 19. Test class and test methods for MovieManager
We can do more things using Rhino Mocks than creating simple mock objects.
Summary
In this article we saw about how to create unit test cases using NUnit and also how to mock the dependencies of a class under test using Rhino Mocks. When you create application that should be better unit testable then the dependency between the classes should be loosely coupled using interfaces. If you are going to create test cases for an existing source-code and it can’t be modfied then for creating mock objects you have to go for advanced frameworks like TypeMock.NET.