Create and Arrange Mocks at the Same Time
The built-in feature for creating mocks by specific arrangement saves time when it comes to tiresome set up of arrangements. This functionality allows you to create mocks of a certain class (the system under test) and to arrange their behavior at the same time through the Mock.CreateLike method.
Using Mock.CreateLike
Let's take the following system under test for example:
Sample Setup
public interface IInstallInfo
{
List<IInstallPackage> InstallPackages { get; set; }
}
public interface IInstallPackage
{
IInstallerInfo Installer { get; set; }
}
public interface IInstallerInfo
{
string Name { get; set; }
DetectionInfoBase BlockingCondition { get; set; }
}
public class DetectionInfoBase
{
public string Name { get; set; }
}
Public Interface IInstallInfo
Property InstallPackages As List(Of IInstallPackage)
End Interface
Public Interface IInstallPackage
Property Installer As IInstallerInfo
End Interface
Public Interface IInstallerInfo
Property Name As String
Property BlockingCondition As DetectionInfoBase
End Interface
Public Class DetectionInfoBase
Public Property Name As String
Get
Return m_Name
End Get
Set(value As String)
m_Name = value
End Set
End Property
Private m_Name As String
End Class
For simple tests with few arrangements, this provides only marginal benefit. The real benefit comes with complex tests with multiple arrangements, like the one demonstrated in Example 1.
Example 1: Create and Arrange with Mock.Create and Mock.Arrange
[TestMethod]
public void TestWithALotOfArrangements()
{
var blockingCondition1 = Mock.Create<DetectionInfoBase>();
Mock.Arrange(() => blockingCondition1.Name).Returns("foo");
var installer1 = Mock.Create<IInstallerInfo>();
Mock.Arrange(() => installer1.BlockingCondition).Returns(blockingCondition1);
Mock.Arrange(() => installer1.Name).Returns("blocked1");
var package1 = Mock.Create<IInstallPackage>();
Mock.Arrange(() => package1.Installer).Returns(installer1);
var blockingCondition2 = Mock.Create<DetectionInfoBase>();
Mock.Arrange(() => blockingCondition2.Name).Returns("bar");
var installer2 = Mock.Create<IInstallerInfo>();
Mock.Arrange(() => installer2.BlockingCondition).Returns(blockingCondition2);
Mock.Arrange(() => installer2.Name).Returns("blocked2");
var package2 = Mock.Create<IInstallPackage>();
Mock.Arrange(() => package2.Installer).Returns(installer2);
var installInfo = Mock.Create<IInstallInfo>();
Mock.Arrange(() => installInfo.InstallPackages).ReturnsCollection(new List<IInstallPackage> { package1, package2 });
}
<TestMethod>
Public Sub TestWithALotOfArrangements()
Dim blockingCondition1 = Mock.Create(Of DetectionInfoBase)()
Mock.Arrange(Function() blockingCondition1.Name).Returns("foo")
Dim installer1 = Mock.Create(Of IInstallerInfo)()
Mock.Arrange(Function() installer1.BlockingCondition).Returns(blockingCondition1)
Mock.Arrange(Function() installer1.Name).Returns("blocked1")
Dim package1 = Mock.Create(Of IInstallPackage)()
Mock.Arrange(Function() package1.Installer).Returns(installer1)
Dim blockingCondition2 = Mock.Create(Of DetectionInfoBase)()
Mock.Arrange(Function() blockingCondition2.Name).Returns("bar")
Dim installer2 = Mock.Create(Of IInstallerInfo)()
Mock.Arrange(Function() installer2.BlockingCondition).Returns(blockingCondition2)
Mock.Arrange(Function() installer2.Name).Returns("blocked2")
Dim package2 = Mock.Create(Of IInstallPackage)()
Mock.Arrange(Function() package2.Installer).Returns(installer2)
Dim expected As New List(Of IInstallPackage)
expected.Add(package1)
expected.Add(package2)
Dim installInfo = Mock.Create(Of IInstallInfo)()
Mock.Arrange(Function() installInfo.InstallPackages).ReturnsCollection(expected)
End Sub
To first explain the functionality in more details, let’s see Example 2 which shows how you can rewrite the above test using the capabilities provided by Mock.CreateLike
.
Example 2: Using Mock.CreateLike
[TestMethod]
public void ShouldExplainStepByStep()
{
// Create mock, whose Name property returns "blocked1". The == operator here is equivalent to calling .Returns("blocked1") while arranging inst.Name
Mock.CreateLike<IInstallerInfo>(inst => inst.Name == "blocked1");
// Create inner mocks recursively and set Installer.Name to return "blocked1"
Mock.CreateLike<IInstallPackage>(pkg => pkg.Installer.Name == "blocked1");
// Create inner mocks recursively and arrange several return values. The && operator is used to make a list of several arrangements.
Mock.CreateLike<IInstallPackage>(pkg => pkg.Installer.Name == "blocked1" && pkg.Installer.BlockingCondition.Name == "foo");
// Arrange a property to return a list of mocks. Mock.Create can be called recursively within the expression.
Mock.CreateLike<IInstallInfo>(me => me.InstallPackages == new List<IInstallPackage> { Mock.Create<IInstallPackage>() });
}
<TestMethod> _
Public Sub ShouldExplainStepByStep()
' Create mock, whose Name property returns "blocked1". The == operator here is equivalent to calling .Returns("blocked1") while arranging inst.Name
Mock.CreateLike(Of IInstallerInfo)(Function(inst) inst.Name = "blocked1")
' Create inner mocks recursively and set Installer.Name to return "blocked1"
Mock.CreateLike(Of IInstallPackage)(Function(pkg) pkg.Installer.Name = "blocked1")
' Create inner mocks recursively and arrange several return values. The && operator is used to make a list of several arrangements.
Mock.CreateLike(Of IInstallPackage)(Function(pkg) pkg.Installer.Name = "blocked1" AndAlso pkg.Installer.BlockingCondition.Name = "foo")
' Arrange a property to return a list of mocks. Mock.Create can be called recursively within the expression.
' Expected list.
Dim myList = New List(Of IInstallPackage)()
myList.Add(Mock.Create(Of IInstallPackage)())
' CreateLike
Mock.CreateLike(Of IInstallInfo)(Function([me]) [me].InstallPackages Is New List(Of IInstallPackage)(myList))
End Sub
The API is even more powerful and you can define all the arrangements from the above test in a single line:
Example 3: Using Mock.CreateLike to create and arrange memebers on a single line
[TestMethod]
public void TestShowingTheMocksByExampleUsability()
{
var installInfo = Mock.CreateLike<IInstallInfo>(me =>
me.InstallPackages == new List<IInstallPackage>
{
Mock.CreateLike<IInstallPackage>(pkg => pkg.Installer.Name == "blocked1"
&& pkg.Installer.BlockingCondition.Name == "foo"),
Mock.CreateLike<IInstallPackage>(pkg => pkg.Installer.Name == "blocked2"
&& pkg.Installer.BlockingCondition.Name == "bar"),
});
}
<TestMethod> _
Public Sub TestShowingTheMocksByExampleUsability()
Dim expectedList As New List(Of IInstallPackage)
expectedList.Add(Mock.CreateLike(Of IInstallPackage)(Function(pkg) pkg.Installer.Name = "blocked1" _
AndAlso pkg.Installer.BlockingCondition.Name = "foo"))
expectedList.Add(Mock.CreateLike(Of IInstallPackage)(Function(pkg) pkg.Installer.Name = "blocked2" _
AndAlso pkg.Installer.BlockingCondition.Name = "bar"))
Dim installInfo = Mock.CreateLike(Of IInstallInfo)(Function([me]) [me].InstallPackages Is expectedList)
End Sub
The syntax reflects the hierarchical structure of the complex arrangement much better and makes trivial arrangements (like for the values of properties) easy.
Using Matchers with Mock.CreateLike
When creating mocks with Mock.CreateLike
, you can also use argument matchers in method arguments, just like in regular Mock.Arrange()
expressions.
Sample setup
public interface IEqualityComparer
{
bool Equals(object a, object b);
}
Public Interface IEqualityComparer
Function Equals(a As Object, b As Object) As Boolean
End Interface
Example 4: Mock.CreateLike with argument matchers
[TestMethod]
public void ShouldUseMatchers()
{
// Create a mock and arrange the Equals method, when called with any arguments, to forward the call to Object.Equals with the given arguments
Mock.CreateLike<IEqualityComparer>(cmp => cmp.Equals(Arg.AnyObject, Arg.AnyObject) == Object.Equals(Param._1, Param._2));
}
<TestMethod> _
Public Sub ShouldUseMatchers()
' Create a mock and arrange the Equals method, when called with any arguments, to forward the call to Object.Equals with the given arguments
Mock.CreateLike(Of IEqualityComparer)(Function(cmp) cmp.Equals(Arg.AnyObject, Arg.AnyObject) = [Object].Equals(Param._1, Param._2))
End Sub
You can even refer to the arguments of the method when specifying its return value, like demonstrated in Example 4. In this example, the Param._1
and Param._2
bits are placeholders for the actual arguments passed to the arranged method. In other words, the arrangement means that Object.Equals
will be called instead of cmp.Equals
, and the parameters of cmp.Equals
will be transferred as parameters of Object.Equals
.
When you need to use argument matchers and the type you need is not present in the Arg.Any~ properties, you can also use a custom type by matching it using the
Arg.IsAny<T>()
method.
Using CreateLike With Additional Expectation Clauses
With Mock.CreateLike
you can arrange return values. If you would like to set additional expectation clauses like DoNothing
, DoInstead
, Occurs
, etc., you should use Mock.Arrange
. To show how you can do that, let’s use the setup shown below.
Sample setup
public interface IConnection
{
string Driver { get; }
bool Open(string parameters);
}
Public Interface IConnection
ReadOnly Property Driver As String
Function Open(parameters As String) As Boolean
End Interface
For simple cases, you can create a test like the one in Example 5.
Example 5: Arrange return values
[TestMethod]
public void SampleTest()
{
var conn = Mock.CreateLike<IConnection>(me => me.Driver == "MSSQL" && me.Open(Arg.AnyString) == true);
Assert.IsTrue(conn.Open(@".\SQLEXPRESS"));
}
<TestMethod> _
Public Sub SampleTest()
Dim conn = Mock.CreateLike(Of IConnection)(Function([me]) [me].Driver = "MSSQL" AndAlso [me].Open(Arg.AnyString) = True)
Assert.IsTrue(conn.Open(".\SQLEXPRESS"))
End Sub
However, if you need to add expectations to Open()
, you cannot with the above syntax. You should use Mock.Arrange
to add them.
Example 6: Arrange return values and set additional expectations
[TestMethod]
public void ShouldUseCreateLikeAlongWithStandartArrangements()
{
var conn = Mock.CreateLike<IConnection>(me => me.Driver == "MSSQL");
Mock.Arrange(() => conn.Open(Arg.AnyString)).Returns(true).MustBeCalled();
Assert.IsTrue(conn.Open(@".\SQLEXPRESS"));
Mock.Assert(conn);
}
<TestMethod> _
Public Sub SampleTestusingMockCreate()
Dim conn = Mock.CreateLike(Of IConnection)(Function([me]) [me].Driver = "MSSQL")
Mock.Arrange(Function() conn.Open(Arg.AnyString)).Returns(True).MustBeCalled()
Assert.IsTrue(conn.Open(".\SQLEXPRESS"))
Mock.Assert(conn)
End Sub
At any time, for any mock, you can add additional arrangements using
Mock.Arrange
for mock objects created withMock.CreateLike
.