Mocking an internal interface with InternalsVisibleTo in C#
Previously, posted instructions for setting this up.
How to Mock an internal interface with NMock2?
Attached is a sample solution that demonstrates actually implementing this.
Archive for the ‘Unit Tests’ Category.
Previously, posted instructions for setting this up.
How to Mock an internal interface with NMock2?
Attached is a sample solution that demonstrates actually implementing this.
I was attempting to mock an internal interface with NMock2 and no matter what I tried I continued to get the following failure.
Test 'M:ExampleOfIVT.PersonTests.FirstTest' failed: Type is not public, so a proxy cannot be generated. Type: ExampleOfIVT.IPerson Castle.DynamicProxy.Generators.GeneratorException: Type is not public, so a proxy cannot be generated. Type: ExampleOfIVT.IPerson at Castle.DynamicProxy.DefaultProxyBuilder.AssertValidType(Type target) at Castle.DynamicProxy.DefaultProxyBuilder.CreateInterfaceProxyTypeWithoutTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options) at NMock2.Monitoring.CastleMockObjectFactory.GetProxyType(CompositeType compositeType) at NMock2.Monitoring.CastleMockObjectFactory.CreateMock(Mockery mockery, CompositeType typesToMock, String name, MockStyle mockStyle, Object[] constructorArgs) at NMock2.Internal.MockBuilder.Create(Type primaryType, Mockery mockery, IMockObjectFactory mockObjectFactory) at NMock2.Mockery.NewMock[TMockedType](IMockDefinition definition) at NMock2.Mockery.NewMock[TMockedType](Object[] constructorArgs) PersonTests.cs(59,0): at ExampleOfIVT.PersonTests.FirstTest()
Obviously I have a project, ExampleOfIVT, and a test project, ExampleOfIVTTests.
All the Google searching suggested that I should be adding these lines to my AssemblyInfo.cs file in the ExampleOfIVT project but these line did NOT work.
Please not that I downloaded NMock2 from here: http://sourceforge.net/projects/nmock2/
[assembly: InternalsVisibleTo("Mocks")] [assembly: InternalsVisibleTo("MockObjects")]
Turns out that they have changed to use Castle.DynamicProxy and so the only assembly I needed was this one.
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
Return to C# Unit Test Tutorial
Continued from Beginning Unit Testing Tutorial in C# with NUnit (Part 1)
[Test] public void Add_1_And_2_Test() { // Step 1 - Arrange (Basically create the objects) SimpleAddSubtractTool tool = new SimpleAddSubtractTool(); // Step 2 - Act int actual = tool.Add(1, 2); // Step 3 - Assert int expected = 3; // This makes it clear what you expect. Assert.AreEqual(expected, actual); }
You are now ready to run your test.
You might think you can just right-click on the project and choose Run Tests. However, by default you cannot do this. To add a plugin called TestDriven.NET that provides this right-click Run Tests functionality, see this post.
How to run a Unit Tests in Visual Studio?
Now you should be able to run your tests.
Code Coverage is basically the number of lines of code tested divided by the total number of lines of code. If you have 10 lines of code but your test only touches five lines of code, you have 50% code coverage. Obviously the goal is 100%.
Again, by default you cannot just right-click and run the Unit Tests unless you have installed TestDriven.NET.
You should now see your code coverage. The Add() method should be 100% covered.
You may think that you are good to go. You have unit tests, and your code is 100% covered (based on line coverage). Well, there are bugs in the code above and you haven’t found them, so you are not done. In order to find these bugs you should research the possible parameter values. You will find some more tests to run. I coined the term Parameter Value Coverage (PVC). Most code coverage tools completely ignore PVC.
Both parameters are of the type int or System.Int32. So these are 32 bit integers.
How many possible values should be tested to have 100% PVC? You should have at least these five:
Well, write unit tests to answer these questions. Adding 1 to 2,147,483,647 will fail if the expected value is the positive integer 2,147,483,648. In fact, the answer is the negative integer -2,147,483,648. Figure out why this is. Determine where the bug is, then decide how to handle it.
[Test] public void Add_Int32MaxValue_and_1_Test() { // Step 1 - Arrange (Basically create the objects and prepare the test) SimpleAddSubtractTool tool = new SimpleAddSubtractTool(); // Step 2 - Act (Bacically call the method) int actual = tool.Add(int.MaxValue, 1); // Step 3 - Assert (Make sure what you expect is true) int expected = 2147483648; // This won't even compile... Assert.AreEqual(expected, actual); }
It is up to you to decide how to fix this bug, as the right way to fix this bug is not part of this article.
Return to C# Unit Test Tutorial
So how do you get started with Unit Testing? Well, you need a Unit Testing framework and some basic know how. This article will help you with both.
NUnit is a commonly used testing framework for C#. Well, NUnit has a guide but I found it incomplete for a newbie, though perfectly acceptable for an experienced developer.
http://nunit.org/index.php?p=quickStart&r=2.6
Lets assume that you have a project with this class:
namespace NUnitBasics { public class SimpleAddSubtractTool { public int Add(int value1, int value2) { return value1 + value2; } public int Subtract(int value1, int value2) { return value1 - value2; } } }
You need to test these methods and find bugs in them and despite how simple they appear, there are bugs in the above methods.
You can do this manually, as described below, or you can download my templates: NUnit Project Template for Visual Studio
You can add a class and manually change it to be an NUnit test class as described below, or you can download my template: NUnit Item Template for Visual Studio
using System; using System.Collections.Generic; using System.Linq; using System.Text; using NUnit.Framework; namespace NUnitBasicsTests { /// <summary> /// A test class for ... /// </summary> [TestFixture] public class SampleTest { #region Setup and Tear down /// <summary> /// This runs only once at the beginning of all tests and is used for all tests in the /// class. /// </summary> [TestFixtureSetUp] public void InitialSetup() { } /// <summary> /// This runs only once at the end of all tests and is used for all tests in the class. /// </summary> [TestFixtureTearDown] public void FinalTearDown() { } /// <summary> /// This setup funcitons runs before each test method /// </summary> [SetUp] public void SetupForEachTest() { } /// <summary> /// This setup funcitons runs after each test method /// </summary> [TearDown] public void TearDownForEachTest() { } #endregion } }
You are now ready to create your first test.
Note: For this basic class you can actually delete the Setup and Tear down region as it won’t be used, however, it is good to know how to use it when you get started.
Return to C# Unit Test Tutorial
NUnit does not currently have a plugin for Visual Studio that allows you to run test right from Visual Studio. However, it can be done.
You can use a number of tools but the tool I prefer is TestDriven.NET.
Notice that you can also run tests in the Debugger and with Coverage and with Performance.
The software can be downloaded here. Sorry, there is not an installer yet.
That is it.
Try these sample method signatures.
public void MyFunction(string inString, bool inBool) public void MyFunction(string inString, int inInt, bool inBool) public void MyFunction(Person inPerson, string inString, int inInt) { // Do some stuf }
Return to C# Unit Test Tutorial
Here is an NUnit Project Template for Visual Studio.
Here is an NUnit Item Template for Visual Studio.
The following are Unit Test best practices and guidelines:
I hope this list helps you.
If you have a best practice not on this list, or you want to comment on one of the items, or even disagree, please comment.
Return to C# Unit Test Tutorial
Interface-based design leads to good Unit Tests because you can more easily have a separation of concerns, limit the test to one object, and eliminate dependencies by not having to have “real” dependencies available to test a portion of your code.
I am going to explain this by example.
First, let me remind you of some rules for writing good Unit Tests:
Imagine you have code that tests authenticating to a UNC path. You have these two classes:
BusinessObject contains an instance of NetworkShare. The code is not really significant as this is theoretical. We’ll get to code in the next few articles.
You need to write Unit Tests for BusinessObject.cs.
Why is this a problem? Because it requires a system with a UNC share to run the class and so testing is difficult.
Now, imagine that BusinessObject.cs didn’t actually have an instance of NetworkShare, but instead an Interface was created called IAuthenticateRemotely. And Your code now includes these files.
Now BusinessObject has an instance of IAuthenticateRemotely instead of an instance of NetworkShare. Now you can write Unit Tests for the BusinessObject class by simply creating any fake class in your Unit Test code that implements IAuthenticateRemotely. Your test class would simply be a fake. Lets say the IAuthenticateRemotely had a bool Authenticate() function. You would create a fake class for your test and implement this function as follows.
public bool Authenticate() { return true; }
Notice that the function doesn’t actually do anything. That is because a Unit Test for the BusinessObject class only needs to test the BusinessObject class.
Please feel free to make any comments as necessary.
Return to C# Unit Test Tutorial
As mentioned in the previous Unit Test post (Unit Testing Registry access with RhinoMocks and SystemWrapper) it important to be able to Unit Test code that access the registry without really accessing the registry.
Prerequisites
You may have already done this when reading the previous post, if not do so now.
Ok, so here we are going to start with an example. Here is an object that I wrote myself that uses the registry to check on which versions of .NET Framework are installed. It is currently using Registry and RegistryKey directly.
using System; using Microsoft.Win32; namespace SystemInfo { public class DotNetFramework { #region Constant members public const string REGPATH_DOTNET11_VERSION = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v1.1.4322"; public const string REGPATH_DOTNET20_VERSION = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727"; public const string REGPATH_DOTNET30_VERSION = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.0"; public const string REGPATH_DOTNET35_VERSION = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5"; public const string REGPATH_DOTNET40_VERSION_CLIENT = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Client"; public const string REGPATH_DOTNET40_VERSION = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"; #endregion #region Properties /// <summary> /// This returns true if .NET 4 Full is installed. /// </summary> public bool FoundDotNet4 { get { return HasDotNetFramework(REGPATH_DOTNET40_VERSION, 0); } } /// <summary> /// This returns true if .NET 4 Client is installed. /// </summary> public bool FoundDotNet4Client { get { return HasDotNetFramework(REGPATH_DOTNET40_VERSION_CLIENT, 0); } } /// <summary> /// This returns true if .NET 3.5 with SP1 is installed. /// </summary> public bool FoundDotNet35SP1 { get { return HasDotNetFramework(REGPATH_DOTNET35_VERSION, 1); } } /// <summary> /// This returns true if .NET 3.5 is installed. /// </summary> public bool FoundDotNet35 { get { return HasDotNetFramework(REGPATH_DOTNET35_VERSION, 0); } } /// <summary> /// This returns true if .NET 3.0 with SP2 is installed. /// </summary> public bool FoundDotNet30SP2 { get { return HasDotNetFramework(REGPATH_DOTNET30_VERSION, 2); } } /// <summary> /// This returns true if .NET 3.0 with SP1 is installed. /// </summary> public bool FoundDotNet30SP1 { get { return HasDotNetFramework(REGPATH_DOTNET30_VERSION, 1); } } /// <summary> /// This returns true if .NET 3.0 is installed. /// </summary> public bool FoundDotNet30 { get { return HasDotNetFramework(REGPATH_DOTNET30_VERSION, 0); } } /// <summary> /// This returns true if .NET 2.0 with SP2 is installed. /// </summary> public bool FoundDotNet20SP2 { get { return HasDotNetFramework(REGPATH_DOTNET20_VERSION, 2); } } /// <summary> /// This returns true if .NET 2.0 with SP1 is installed. /// </summary> public bool FoundDotNet20SP1 { get { return HasDotNetFramework(REGPATH_DOTNET20_VERSION, 1); } } /// <summary> /// This returns true if .NET 2.0 is installed. /// </summary> public bool FoundDotNet20 { get { return HasDotNetFramework(REGPATH_DOTNET20_VERSION, 0); } } /// <summary> /// This returns true if .NET 1.1 is installed. /// </summary> public bool FoundDotNet11 { get { return HasDotNetFramework(REGPATH_DOTNET11_VERSION, 0); } } #endregion public bool HasDotNetFramework(string dotNetRegPath, int expectedServicePack) { bool retVal = false; try { RegistryKey localKey = Registry.LocalMachine.OpenSubKey(dotNetRegPath); if (localKey != null) { int? isInstalled = localKey.GetValue("Install") as int?; if (isInstalled != null) { if (isInstalled == 1) { if (expectedServicePack > 0) { if ((int)localKey.GetValue("SP") >= expectedServicePack) retVal = true; } else { retVal = true; } } } } return retVal; } catch { return retVal; } } } }
using System; using SystemInterface.Microsoft.Win32; using SystemWrapper.Microsoft.Win32;
RegistryKey localKey = Registry.LocalMachine.OpenSubKey(dotNetRegPath);
…changes to this line.
IRegistryKey localKey = new RegistryWrap().LocalMachine.OpenSubKey(dotNetRegPath);
Create a property or function that allows you to replace the object you are mocking so you can pass in an object that is mocked.
This is done for you to some extent for registry access by using the IAccessTheRegistry interface.
#region IAccessTheRegistry Members public IRegistryKey BaseKey { get { if (null == _BaseKey) _BaseKey = new RegistryWrap().LocalMachine; return _BaseKey; } } private IRegistryKey _BaseKey; public void ChangeBaseKey(IRegistryKey inBaseKey) { _BaseKey = inBaseKey; } #endregion
Note: Notice that the Property is read only. Instead of enabling the set ability, a function is created to allow you to change the IRegistryKey instance. This is by design and keeps you from making a mistake such as BaseKey = BaseKey.OpenSubkey(“…”) and replacing the BaseKey.
Note: You may want to look into the “Factory” design pattern.
IRegistryKey localKey = BaseKey.OpenSubKey(dotNetRegPath);
You may have already done this when reading the previous post, if not do so now.
In this step you need to download RhinoMocks and reference it with your Unit Test project. If you don’t have a test project, create one. Your release project won’t need it.
For this, you have to understand and know how to use RhinoMocks and this takes some learning. For this we need an example.
using Rhino.Mocks;
#region Test Helper Functions /// <summary> /// Mock to pretend to be this registry subkey: /// Hive: HKLM /// /// No stubbing is preconfigured /// </summary> /// <returns>IRegistryKey</returns> private IRegistryKey CreateMockOfHKLM() { // Mock to pretend to be this registry key: // Hive: HKLM IRegistryKey hklmMock = MockRepository.GenerateMock<IRegistryKey>(); hklmMock.Stub(x => x.Name).Return("HKEY_LOCAL_MACHINE"); return hklmMock; } /// <summary> /// Mock to pretend to be this registry subkey: /// Hive: HKLM /// /// It has stubs preconfigured /// </summary> /// <returns>IRegistry that is a mock of HKLM with IIS Version stubs.</returns> private IRegistryKey CreateDotNetRegistryOpenSubKeyMock(string inSubkey, bool inIsInstalled = true, int? inSP = null) { // Mock to pretend to be this registry subkey: // Hive: HKLM IRegistryKey hklmMock = CreateMockOfHKLM(); // Allow to test a key that doesn't exist (null) or one that does. IRegistryKey dotNetMock = null; if (inIsInstalled) { dotNetMock = CreateDotNetRegistryGetValueMock(inSubkey, inSP); } // Stubs using hklmMock to open and return this registry key // and return dotNetMock: hklmMock.Stub(x => x.OpenSubKey(inSubkey)).Return(dotNetMock); return hklmMock; } private IRegistryKey CreateDotNetRegistryGetValueMock(string inSubkey, int? inSP) { // Mock to pretend to be this subkey passed in: // Hive: HKLM // SubKey: HKEY_LOCAL_MACHINE\" + inSubkey IRegistryKey dotNetMock = MockRepository.GenerateMock<IRegistryKey>(); dotNetMock.Stub(x => x.Name).Return(@"HKEY_LOCAL_MACHINE\" + inSubkey); // Stubs checking the available registry properties: // Hive: HKLM // SubKey: HKEY_LOCAL_MACHINE\" + inSubkey // Properties: "Install", "SP" dotNetMock.Stub(x => x.GetValueNames()).Return(new String[] { "Install", "SP" }); // Stubs checking this registry: // Hive: HKLM // SubKey: HKEY_LOCAL_MACHINE\" + inSubkey // Property: Install // Value: 1 - If not installed the whole key shouldn't even exist so it should always be 1 dotNetMock.Stub(x => x.GetValue("Install")).Return(1); if (null != inSP) { // Stubs checking this registry: // Hive: HKLM // SubKey: HKEY_LOCAL_MACHINE\" + inSubkey // Property: SP // Value: null or 1, 2, 3, ... dotNetMock.Stub(x => x.GetValue("SP")).Return(inSP); } return dotNetMock; } #endregion
Here is the final test file.
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Rhino.Mocks; using SystemInfo; using SystemInterface.Microsoft.Win32; namespace DotNetFrameworkTest { /// <summary> ///This is a test class for DotNetFrameworkTest and is intended ///to contain all DotNetFrameworkTest Unit Tests ///</summary> [TestClass()] public class DotNetFrameworkTest { #region Constructor tests /// <summary> ///A test for DotNetFramework Constructor ///</summary> [TestMethod()] public void DotNetFrameworkConstructorTest() { DotNetFramework target = new DotNetFramework(); Assert.IsNotNull(target); } #endregion #region IAccessTheRegistry interface items test /// <summary> ///A test for ChangeBaseKey ///</summary> [TestMethod()] public void BaseKeyAndChangeBaseKeyTest() { DotNetFramework target = new DotNetFramework(); // TODO: Initialize to an appropriate value IRegistryKey defaultBaseKey = target.BaseKey; IRegistryKey inBaseKey = MockRepository.GenerateMock<IRegistryKey>(); target.ChangeBaseKey(inBaseKey); // Make sure the newly assigned is not the same as the original Assert.AreNotEqual(target.BaseKey, defaultBaseKey); // Make sure that the assignment worked Assert.AreEqual(target.BaseKey, inBaseKey); } #endregion #region FoundDotNet11 tests /// <summary> ///A test for FoundDotNet11 mocking the key so it should return true ///</summary> [TestMethod()] public void FoundDotNet11_Test_KeyExists() { bool isInstalled = true; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET11_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = true; bool actual = target.FoundDotNet11; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET11_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET11_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasNotCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet11 mocking the lack of a key so it should return false ///</summary> [TestMethod()] public void FoundDotNet11_Test_KeyDoesNotExist() { bool isInstalled = false; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET11_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet11; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET11_VERSION)); Assert.IsNull(key.OpenSubKey(DotNetFramework.REGPATH_DOTNET11_VERSION)); } #endregion #region FoundDotNet20 tests /// <summary> ///A test for FoundDotNet20 mocking the key so it should return true ///</summary> [TestMethod()] public void FoundDotNet20_Test_KeyExists() { bool isInstalled = true; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET20_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = true; bool actual = target.FoundDotNet20; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasNotCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet20 mocking the lack of a key so it should return false ///</summary> [TestMethod()] public void FoundDotNet20_Test_KeyDoesNotExist() { bool isInstalled = false; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET20_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet20; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION)); Assert.IsNull(key.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION)); } #endregion #region FoundDOtNet20SP1 tests /// <summary> ///A test for FoundDotNet20SP1 mocking the key so it should return true ///</summary> [TestMethod()] public void FoundDotNet20SP1_Test_KeyExistsWithSP1() { bool isInstalled = true; int? sp = 1; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET20_VERSION, isInstalled, sp); target.ChangeBaseKey(key); bool expected = true; bool actual = target.FoundDotNet20SP1; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet20SP1 mocking the key so it should return false due to no service pack ///</summary> [TestMethod()] public void FoundDotNet20SP1_Test_KeyExistsWithoutSP1() { bool isInstalled = true; int? sp = null; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET20_VERSION, isInstalled, sp); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet20SP1; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet20SP1 mocking the lack of a key so it should return false ///</summary> [TestMethod()] public void FoundDotNet20SP1_Test_KeyDoesNotExist() { bool isInstalled = false; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET20_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet20SP1; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION)); Assert.IsNull(key.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION)); } #endregion #region FoundDOtNet20SP2 tests /// <summary> ///A test for FoundDotNet20SP2 mocking the key so it should return true ///</summary> [TestMethod()] public void FoundDotNet20SP2_Test_KeyExistsWithSP2() { bool isInstalled = true; int? sp = 2; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET20_VERSION, isInstalled, sp); target.ChangeBaseKey(key); bool expected = true; bool actual = target.FoundDotNet20SP2; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet20SP2 mocking the key so it should return false because it only has SP1 ///</summary> [TestMethod()] public void FoundDotNet20SP2_Test_KeyExistsWithSP1() { bool isInstalled = true; int? sp = 1; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET20_VERSION, isInstalled, sp); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet20SP2; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet20SP2 mocking the key so it should return false due to no service pack ///</summary> [TestMethod()] public void FoundDotNet20SP2_Test_KeyExistsWithoutSP() { bool isInstalled = true; int? sp = null; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET20_VERSION, isInstalled, sp); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet20SP2; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet20SP2 mocking the lack of a key so it should return false ///</summary> [TestMethod()] public void FoundDotNet20SP2_Test_KeyDoesNotExist() { bool isInstalled = false; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET20_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet20SP2; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION)); Assert.IsNull(key.OpenSubKey(DotNetFramework.REGPATH_DOTNET20_VERSION)); } #endregion #region FoundDotNet30 tests /// <summary> ///A test for FoundDotNet30 mocking the key so it should return true ///</summary> [TestMethod()] public void FoundDotNet30_Test_KeyExists() { bool isInstalled = true; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET30_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = true; bool actual = target.FoundDotNet30; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasNotCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet30 mocking the lack of a key so it should return false ///</summary> [TestMethod()] public void FoundDotNet30_Test_KeyDoesNotExist() { bool isInstalled = false; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET30_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet30; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION)); Assert.IsNull(key.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION)); } #endregion #region FoundDOtNet30SP1 tests /// <summary> ///A test for FoundDotNet30SP1 mocking the key so it should return true ///</summary> [TestMethod()] public void FoundDotNet30SP1_Test_KeyExistsWithSP1() { bool isInstalled = true; int? sp = 1; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET30_VERSION, isInstalled, sp); target.ChangeBaseKey(key); bool expected = true; bool actual = target.FoundDotNet30SP1; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet30SP1 mocking the key so it should return false due to no service pack ///</summary> [TestMethod()] public void FoundDotNet30SP1_Test_KeyExistsWithoutSP() { bool isInstalled = true; int? sp = null; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET30_VERSION, isInstalled, sp); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet30SP1; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet30SP1 mocking the lack of a key so it should return false ///</summary> [TestMethod()] public void FoundDotNet30SP1_Test_KeyDoesNotExist() { bool isInstalled = false; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET30_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet30SP1; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION)); Assert.IsNull(key.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION)); } #endregion #region FoundDOtNet30SP2 tests /// <summary> ///A test for FoundDotNet30SP2 mocking the key so it should return true ///</summary> [TestMethod()] public void FoundDotNet30SP2_Test_KeyExistsWithSP2() { bool isInstalled = true; int? sp = 2; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET30_VERSION, isInstalled, sp); target.ChangeBaseKey(key); bool expected = true; bool actual = target.FoundDotNet30SP2; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet30SP2 mocking the key so it should return false because it only has SP1 ///</summary> [TestMethod()] public void FoundDotNet30SP2_Test_KeyExistsWithSP1() { bool isInstalled = true; int? sp = 1; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET30_VERSION, isInstalled, sp); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet30SP2; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet30SP2 mocking the key so it should return false due to no service pack ///</summary> [TestMethod()] public void FoundDotNet30SP2_Test_KeyExistsWithoutSP() { bool isInstalled = true; int? sp = null; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET30_VERSION, isInstalled, sp); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet30SP2; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet30SP2 mocking the lack of a key so it should return false ///</summary> [TestMethod()] public void FoundDotNet30SP2_Test_KeyDoesNotExist() { bool isInstalled = false; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET30_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet30SP2; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION)); Assert.IsNull(key.OpenSubKey(DotNetFramework.REGPATH_DOTNET30_VERSION)); } #endregion #region FoundDotNet35 tests /// <summary> ///A test for FoundDotNet35 mocking the key so it should return true ///</summary> [TestMethod()] public void FoundDotNet35_Test_KeyExists() { bool isInstalled = true; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET35_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = true; bool actual = target.FoundDotNet35; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET35_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET35_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasNotCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet35 mocking the lack of a key so it should return false ///</summary> [TestMethod()] public void FoundDotNet35_Test_KeyDoesNotExist() { bool isInstalled = false; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET35_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet35; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET35_VERSION)); Assert.IsNull(key.OpenSubKey(DotNetFramework.REGPATH_DOTNET35_VERSION)); } #endregion #region FoundDOtNet35SP1 tests /// <summary> ///A test for FoundDotNet35SP1 mocking the key so it should return true ///</summary> [TestMethod()] public void FoundDotNet35SP1_Test_KeyExistsWithSP1() { bool isInstalled = true; int? sp = 1; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET35_VERSION, isInstalled, sp); target.ChangeBaseKey(key); bool expected = true; bool actual = target.FoundDotNet35SP1; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET35_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET35_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet35SP1 mocking the key so it should return false due to no service pack ///</summary> [TestMethod()] public void FoundDotNet35SP1_Test_KeyExistsWithoutSP() { bool isInstalled = true; int? sp = null; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET35_VERSION, isInstalled, sp); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet35SP1; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET35_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET35_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet35SP1 mocking the lack of a key so it should return false ///</summary> [TestMethod()] public void FoundDotNet35SP1_Test_KeyDoesNotExist() { bool isInstalled = false; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET35_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet35SP1; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET35_VERSION)); Assert.IsNull(key.OpenSubKey(DotNetFramework.REGPATH_DOTNET35_VERSION)); } #endregion #region FoundDotNet 4 Full tests /// <summary> ///A test for FoundDotNet4 (full) mocking the key so it should return true ///</summary> [TestMethod()] public void FoundDotNet4_Full_Test_KeyExists() { bool isInstalled = true; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET40_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = true; bool actual = target.FoundDotNet4; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET40_VERSION)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET40_VERSION); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasNotCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet4 (full) mocking the lack of a key so it should return false ///</summary> [TestMethod()] public void FoundDotNet4_Test_KeyDoesNotExist() { bool isInstalled = false; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET40_VERSION, isInstalled); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet4; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET40_VERSION)); Assert.IsNull(key.OpenSubKey(DotNetFramework.REGPATH_DOTNET40_VERSION)); } #endregion #region FoundDotNet4 Client tests /// <summary> ///A test for FoundDotNet4Client mocking the key so it should return true ///</summary> [TestMethod()] public void FoundDotNet4Client_Test_KeyExists() { bool isInstalled = true; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET40_VERSION_CLIENT, isInstalled); target.ChangeBaseKey(key); bool expected = true; bool actual = target.FoundDotNet4Client; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET40_VERSION_CLIENT)); IRegistryKey innerkey = key.OpenSubKey(DotNetFramework.REGPATH_DOTNET40_VERSION_CLIENT); innerkey.AssertWasCalled(x => x.GetValue("Install")); innerkey.AssertWasNotCalled(x => x.GetValue("SP")); } /// <summary> ///A test for FoundDotNet4 (client) mocking the lack of a key so it should return false ///</summary> [TestMethod()] public void FoundDotNet4Client_Test_KeyDoesNotExist() { bool isInstalled = false; DotNetFramework target = new DotNetFramework(); IRegistryKey key = CreateDotNetRegistryOpenSubKeyMock(DotNetFramework.REGPATH_DOTNET40_VERSION_CLIENT, isInstalled); target.ChangeBaseKey(key); bool expected = false; bool actual = target.FoundDotNet4Client; Assert.AreEqual(actual, expected); key.AssertWasCalled(x => x.OpenSubKey(DotNetFramework.REGPATH_DOTNET40_VERSION_CLIENT)); Assert.IsNull(key.OpenSubKey(DotNetFramework.REGPATH_DOTNET40_VERSION_CLIENT)); } #endregion #region HasDotNetFrameworkTest /// <summary> ///A test for HasDotNetFramework ///</summary> [TestMethod()] public void HasDotNetFrameworkTest() { // No need to test this as all properties test this } #endregion #region Test Helper Functions /// <summary> /// Mock to pretend to be this registry subkey: /// Hive: HKLM /// /// No stubbing is preconfigured /// </summary> /// <returns>IRegistryKey</returns> private IRegistryKey CreateMockOfHKLM() { // Mock to pretend to be this registry key: // Hive: HKLM IRegistryKey hklmMock = MockRepository.GenerateMock<IRegistryKey>(); hklmMock.Stub(x => x.Name).Return("HKEY_LOCAL_MACHINE"); return hklmMock; } /// <summary> /// Mock to pretend to be this registry subkey: /// Hive: HKLM /// /// It has stubs preconfigured /// </summary> /// <returns>IRegistry that is a mock of HKLM with IIS Version stubs.</returns> private IRegistryKey CreateDotNetRegistryOpenSubKeyMock(string inSubkey, bool inIsInstalled = true, int? inSP = null) { // Mock to pretend to be this registry subkey: // Hive: HKLM IRegistryKey hklmMock = CreateMockOfHKLM(); // Allow to test a key that doesn't exist (null) or one that does. IRegistryKey dotNetMock = null; if (inIsInstalled) { dotNetMock = CreateDotNetRegistryGetValueMock(inSubkey, inSP); } // Stubs using hklmMock to open and return this registry key // and return dotNetMock: hklmMock.Stub(x => x.OpenSubKey(inSubkey)).Return(dotNetMock); return hklmMock; } private IRegistryKey CreateDotNetRegistryGetValueMock(string inSubkey, int? inSP) { // Mock to pretend to be this subkey passed in: // Hive: HKLM // SubKey: HKEY_LOCAL_MACHINE\" + inSubkey IRegistryKey dotNetMock = MockRepository.GenerateMock<IRegistryKey>(); dotNetMock.Stub(x => x.Name).Return(@"HKEY_LOCAL_MACHINE\" + inSubkey); // Stubs checking the available registry properties: // Hive: HKLM // SubKey: HKEY_LOCAL_MACHINE\" + inSubkey // Properties: "Install", "SP" dotNetMock.Stub(x => x.GetValueNames()).Return(new String[] { "Install", "SP" }); // Stubs checking this registry: // Hive: HKLM // SubKey: HKEY_LOCAL_MACHINE\" + inSubkey // Property: Install // Value: 1 - If not installed the whole key shouldn't even exist so it should always be 1 dotNetMock.Stub(x => x.GetValue("Install")).Return(1); if (null != inSP) { // Stubs checking this registry: // Hive: HKLM // SubKey: HKEY_LOCAL_MACHINE\" + inSubkey // Property: SP // Value: null or 1, 2, 3, ... dotNetMock.Stub(x => x.GetValue("SP")).Return(inSP); } return dotNetMock; } #endregion } }
Return to C# Unit Test Tutorial
Unit Tests should be fast and simple and they should not touch the system. What we mean by touching the system is any code that actually changes the file system or registry of the build system that runs the Unit Tests. Also any code that touches a remote system such as a database or a web server as these remote system should not have to exist during a Unit Test.
Unit Test Presentation (using Open Office Impress)
Question: So if your code accesses the system, how do you test this code?
Answer: You use Interface-based design and a library that can mock that interface.
Well, it is not exactly simple.
Question: What if you are using some one elses code, such as standard C# libraries in the System or Microsoft namespaces?
Answer: Imagine you need to unit test code that touches the registry. You must do the following steps:
The best solution, long-term, is that Microsoft adds interfaces to the standard .NET libraries and exposes these interfaces so a wrapper isn’t needed. If you agree, vote for this enhancement here: Implement interfaces for your objects so we don’t have use SystemWrapper.codeplex.com
However, since Microsoft hasn’t done this, you have to do it yourself. But all of this probably takes more time than your project will allow you to take, so I am going to try to get you there faster.
Step 1 and Step 2 are done for you, all you have to do is download the dll or source from here:
http://SystemWrapper.codeplex.com
Step 3 and 4 are not done for you but there are examples in the SystemWrapper project.
Step 5 can be done extremely quickly with a free (BSD Licensed) tool called RhinoMocks.
So the new steps become these:
Create a property or function that allows you to replace the object you are mocking so you can pass in an object that is mocked.
Here is an example of how to do this when mocking a RegistryKey object using IRegistryKey.
#region IAccessTheRegistry Members public IRegistryKey BaseKey { get { return _BaseKey ?? (_BaseKey = new RegistryWrap().LocalMachine); } internal set { _BaseKey value; } } private IRegistryKey _BaseKey; #endregion
Notice that the setter is internal. This is because unit tests can usually access an internal method with InternalsVisibleTo. The internal set is hidden for code not in the same assembly and not included in InternalsVisibleTo.
Note: You may want to look into the “Factory” design pattern.
In this step you need to download RhinoMocks and reference it with your Unit Test project. Your release project won’t need it.
For this, you have to understand and know how to use RhinoMocks and this takes some learning.
Instead of trying to explain this here, let’s go straight into the examples.
Forthcoming…
Return to C# Unit Test Tutorial
Unit Testing is the idea of writing additional code, called test code, for the purpose of testing the smallest blocks of production code to the full extent possible to make sure those blocks of code are error free.
Code is usually designed into Classes or Objects. The term “Class” and the term “Object” are usually used interchangeably. Classes are made up of variables and functions that include Constructors, Methods, and Properties but they may also include sub-classes.
For each Object in code, you should have a Unit Test for that object. For each method in an Object, the correlating Unit Test should have a test. In fact, a test should exist that makes sure that every line of your code functions as expected.
Unit Tests are about making sure each line of code functions properly. A Unit Test will execute lines of codes. Imagine you have 100 lines of code and a Unit Test tests only 50 lines of code. You only have 50% code coverage. The other 50% of you code is untested.
Most Unit Test tools, MSTest, MBUnit, NUnit, and others can provide reports on code coverage.
Cyclomatic Complexity is a measurement of how complex your code is. This usually comes up in Unit Testing because in order to get 100% code coverage, one has to tests every possible path of code. The more your code branches, the more complex the code is.
If you have to write twenty tests just to get 100% coverage for a single function, you can bet your cyclomatic complexity is too high and you should probably break the function up or reconsider the design.
Parameter Value Coverage is the idea of checking both expected and unexpected values for a given type. Often bugs occur because testing is not done on an a type that occurs unexpectedly at run time. Often a value is unexpectedly null and null was not handled, causing the infamous null reference exception.
Look at this function. Can you see the bug?
public bool CompareStrings(String inStr1, String inStr2) { return inStr1.Equals(inStr2); }
A System.NullReferenceExecption will occur if inStr1 is null.
How can we find these bugs before a customer reports them? A unit test can catch these bugs. We can make lists of anything we can think of that might react differently.
For example, imagine a function that takes a string. What should we try to test:
Now imaging a function takes an object called PersonName that has a string member for FirstName and LastName value. We should try to test some of the above.
Here is a test that will check for null in the compare strings code.
Note: There is a decision to make here. If comparing two string objects and both are null, do you want to return true, because both are null so they match? Or do you want to return false, because neither are even strings? For the code below, I’ll assume two null strings should not be considered equal.
public bool CompareStrings_Test() { String test1 = null; String test2 = null; bool expected = false; bool actual = CompareStrings(test1, test2); Assert.AreEqual(expected, actual); }
Now your unit test is going to encounter a System.NullReferenceExecption. Assuming you write Unit Tests before you release your code to a customer, you will catch the bug long before the code reaches a customer and fix it.
Here is the fixed function.
public bool CompareStrings(String inStr1, String inStr2) { // If either input values are null, return false if (null == inStr1 || null == inStr2) return false; return inStr1.Equals(inStr2); }
Here is an example object called PersonName. We tried to make it have a little more meat to this object than just a FirstName, LastName password.
PersonName.cs
using System; using System.Collections.Generic; using System.Text; namespace PersonExample { ///<summary> /// An Ojbect representing a Person /// </summary> public class PersonName : IPersonName { #region Constructor public PersonName() { } #endregion #region Properties /// <summary> /// The persons first name. /// </summary> public String FirstName { get; set; } /// <summary> /// The persons Middle Name(s). Some people have multiple middle names. /// So as many middle names as desired can be added. /// </summary> public List MiddleNames { get { if (null == _MiddleNames) _MiddleNames = new List(); return _MiddleNames; } set { _MiddleNames = value; } } private List _MiddleNames; public String LastName { get; set; } #endregion #region Methods /// <summary> /// Converts the name to a string. /// </summary> public override string ToString() { return ToString(NameOrder.LastNameCommaFirstName); } ///<summary> /// Converts the name to a string. /// </summary> /// /// private String ToString(NameOrder inOrder) { switch (inOrder) { case NameOrder.LastNameCommaFirstName: return LastName + ", " + FirstName; case NameOrder.LastNameCommaFirstNameWithMiddleNames: return LastName + ", " + FirstName + " " + MiddleNamesToString(); case NameOrder.FirstNameSpaceLastname: return FirstName + " " + LastName; case NameOrder.FirstNameMiddleNamesLastname: return FirstName + MiddleNamesToString() + LastName; default: return LastName + ", " + FirstName; } } /// <summary> /// Converts the list of middle names to a single string. /// </summary> /// String private String MiddleNamesToString() { StringBuilder builder = new StringBuilder(); bool firstTimeThrough = true; foreach (var name in MiddleNames) { if (firstTimeThrough) firstTimeThrough = false; else builder.Append(" "); builder.Append(name); } return builder.ToString(); } /// <summary> /// Compares the object passed in to see if it is a Person. /// </summary> /// /// True if FirstName and LastName and MiddleNames match, False if the object /// is not a Person or FirstName and LastName and MiddleNames do not match public override bool Equals(object inObject) { PersonName p = inObject as PersonName; if (null == p) return false; else return Equals(p); } /// <summary> /// Compares one PersonName to another PersonName. /// </summary> public bool Equals(IPersonName inPersonName) { return inPersonName.FirstName.Equals(FirstName, StringComparison.CurrentCultureIgnoreCase) && inPersonName.LastName.Equals(LastName, StringComparison.CurrentCultureIgnoreCase); } /// <summary> /// Compares a string to see if it matches a person. /// </summary> public bool Equals(String inString) { string tmpLastNameCommaFirstName = ToString(NameOrder.LastNameCommaFirstName); string tmpLastNameCommaFirstNameWithMiddleNames = ToString(NameOrder.LastNameCommaFirstNameWithMiddleNames); string tmpFirstNameSpaceLastname = ToString(NameOrder.FirstNameSpaceLastname); string FirstNameMiddleNamesLastname = ToString(NameOrder.FirstNameMiddleNamesLastname); return tmpLastNameCommaFirstName.Equals(inString, StringComparison.CurrentCultureIgnoreCase) || tmpLastNameCommaFirstNameWithMiddleNames.Equals(inString, StringComparison.CurrentCultureIgnoreCase) || tmpFirstNameSpaceLastname.Equals(inString, StringComparison.CurrentCultureIgnoreCase) || FirstNameMiddleNamesLastname.Equals(inString, StringComparison.CurrentCultureIgnoreCase); } /// public override int GetHashCode() { return base.GetHashCode(); } #endregion #region Enums /// <summary> /// The order of the names when converted to a string. /// </summary> public enum NameOrder { LastNameCommaFirstName = 0, LastNameCommaFirstNameWithMiddleNames, FirstNameSpaceLastname, FirstNameMiddleNamesLastname } #endregion } }
Ok, so in a separate test project, the following corresponding test class can be created.
PersonNameTest.cs
using Microsoft.VisualStudio.TestTools.UnitTesting; using PersonExample; namespace PersonExample_Test { /// <summary> ///This is a test class for PersonNameTest and is intended ///to contain all PersonNameTest Unit Tests ///</summary> [TestClass()] public class PersonNameTest { #region PersonName() /// <summary> ///A test for PersonName Constructor ///</summary> [TestMethod()] public void PersonName_ConstructorTest() { PersonName person = new PersonName(); Assert.IsNotNull(person); Assert.IsInstanceOfType(person, typeof(PersonName)); Assert.IsNull(person.FirstName); Assert.IsNull(person.LastName); Assert.IsNotNull(person.MiddleNames); } #endregion #region Equals(Object inObject) /// <summary> ///A test for Equals(Object inObject) with matching first and last names ///</summary> [TestMethod()] public void Equals_Obj_Test_Matching_First_Last_Names() { PersonName target = new PersonName() { FirstName = "John", LastName = "Johnson" }; object inObject = new PersonName() { FirstName = "John", LastName = "Johnson" }; bool expected = true; bool actual = target.Equals(inObject); Assert.AreEqual(expected, actual); } /// <summary> ///A test for Equals(Object inObject) with different last name ///</summary> [TestMethod()] public void Equals_Obj_Test_Matching_Different_Last_Names() { PersonName target = new PersonName() { FirstName = "John", LastName = "Johnson" }; object inObject = new PersonName() { FirstName = "John", LastName = "Jameson" }; bool expected = false; bool actual = target.Equals(inObject); Assert.AreEqual(expected, actual); } /// <summary> ///A test for Equals(Object inObject) with different first name ///</summary> [TestMethod()] public void Equals_Obj_Test_Matching_Different_First_Names() { PersonName target = new PersonName() { FirstName = "John", LastName = "Johnson" }; object inObject = new PersonName() { FirstName = "James", LastName = "Johnson" }; bool expected = false; bool actual = target.Equals(inObject); Assert.AreEqual(expected, actual); } /// <summary> ///A test for Equals(Object inObject) when a null is passed in. ///</summary> [TestMethod()] public void Equals_Obj_Test_Null() { PersonName target = new PersonName(); object inObject = null; bool expected = false; bool actual; actual = target.Equals(inObject); Assert.AreEqual(expected, actual); } #endregion #region Equals(String inString) /// <summary> ///A test for Equals(String inString) where inString is null ///</summary> [TestMethod()] public void Equals_String_Test_null() { PersonName target = new PersonName() { FirstName = "Tom", LastName = "Tomison" }; string inString = null; bool expected = false; bool actual; actual = target.Equals(inString); Assert.AreEqual(expected, actual); } /// <summary> ///A test for Equals(String inString) where inString is a match using ///PersonName.NameOrder.FirstNameSpaceLastname ///</summary> [TestMethod()] public void Equals_String_Test_FirstNameSpaceLastname_Match() { PersonName target = new PersonName() { FirstName = "Tom", LastName = "Tomison" }; string inString = "Tom Tomison"; bool expected = true; bool actual; actual = target.Equals(inString); Assert.AreEqual(expected, actual); } /// <summary> ///A test for Equals(String inString) where inString is a match using ///PersonName.NameOrder.LastNameCommaFirstName ///</summary> [TestMethod()] public void Equals_String_Test_LastNameCommaFirstName_Match() { PersonName target = new PersonName() { FirstName = "Tom", LastName = "Tomison" }; string inString = "Tomison, Tom"; bool expected = true; bool actual; actual = target.Equals(inString); Assert.AreEqual(expected, actual); } #endregion // TODO: Finish testing this object } }
For more learning, answer the following questions:
Return to C# Unit Test Tutorial