Test Your Application with JustMock
This topic will guide you through several simple steps to enable easier testing of your applications by using Telerik® JustMock. You will understand a simple principle called Arrange/Act/Assert and get familiar with core methods and properties from the framework, which are used in the most common testing scenarios.
Make sure to go through Installation and Setup and Add JustMock in Your Test Project to setup your environment and project before proceeding further. JustMock API Basics contains basic examples that this article extends.
To illustrate the use of JustMock in the next examples, we will use a sample warehouse and a dependent order object. The warehouse holds inventories of different products. An order contains a product and a quantity.
The warehouse interface and the order class look like this:
public delegate void ProductRemoveEventHandler(string productName, int quantity);
public interface Iwarehouse
{
event ProductRemoveEventHandler ProductRemoved;
string Manager { get; set; }
bool HasInventory(string productName, int quantity);
void Remove(string productName, int quantity);
}
public class Order
{
public Order(string productName, int quantity)
{
this.ProductName = productName;
this.Quantity = quantity;
}
public string ProductName { get; private set; }
public int Quantity { get; private set; }
public bool IsFilled { get; private set; }
public void Fill(Iwarehouse warehouse)
{
if (warehouse.HasInventory(this.ProductName, this.Quantity))
{
warehouse.Remove(this.ProductName, this.Quantity);
}
}
public virtual string Receipt(DateTime orderDate)
{
return string.Format("Ordered {0} {1} on {2}", this.Quantity, this.ProductName, orderDate.ToString("d"));
}
}
Public Delegate Sub ProductRemovedEventHandler(productName As String, quantity As Integer)
Public Interface IWarehouse
Event ProductRemoved As ProductRemovedEventHandler
Property Manager() As String
Function HasInventory(productName As String, quantity As Integer) As Boolean
Sub Remove(productName As String, quantity As Integer)
End Interface
Public Class Order
Public Sub New(productName As String, quantity As Integer)
Me.ProductName = productName
Me.Quantity = quantity
End Sub
Public Property ProductName() As String
Get
Return m_ProductName
End Get
Private Set(value As String)
m_ProductName = value
End Set
End Property
Private m_ProductName As String
Public Property Quantity() As Integer
Get
Return m_Quantity
End Get
Private Set(value As Integer)
m_Quantity = value
End Set
End Property
Private m_Quantity As Integer
Public Property IsFilled() As Boolean
Get
Return m_IsFilled
End Get
Private Set(value As Boolean)
m_IsFilled = value
End Set
End Property
Private m_IsFilled As Boolean
Public Sub Fill(warehouse As IWarehouse)
If warehouse.HasInventory(Me.ProductName, Me.Quantity) Then
warehouse.Remove(Me.ProductName, Me.Quantity)
IsFilled = True
End If
End Sub
Public Overridable Function Receipt(orderDate As DateTime) As String
Return String.Format("Ordered {0} {1} on {2}", Me.Quantity, Me.ProductName, orderDate.ToString("d"))
End Function
End Class
Arrange Act Assert (AAA) is a pattern for arranging and formatting code in Unit Test methods. It is used in all samples shown in this documentation. Refer to the Arrange Act Assert topic to learn about AAA.
Methods
Get familiar with the JustMock basics such as how to create mock instance with
Mock.Create<>
, how to arrange withMock.Arrange
and how to assert withMock.Assert
in Step 3 - JustMock API Basics.
There are a number of additional handy methods that you can use to make your tests more complete and easy to write.
DoInstead
You can use the DoInstead method when you want to change the behavior of a method when it is called by replacing it with a custom action. To illustrate how you can use the DoInstead
method, we will use the HasInventory method of the IWarehouse interface defined in the beginning of this topic. In the next example, we arrange that when the warehouse’s HasInventory
method is called with parameters "Camera" and 2, we will execute the action "() => called = true" instead of calling the actual implementation of the method.
[TestMethod]
public void DoInstead_TestMethod()
{
//Arrange
var warehouse = Mock.Create<Iwarehouse>();
var order = new Order("Camera", 2);
bool called = false;
Mock.Arrange(() => warehouse.HasInventory("Camera", 2)).DoInstead(() => called = true);
//Act
order.Fill(warehouse);
//Assert
Assert.IsTrue(called);
}
<TestMethod()>
Public Sub DoInstead_TestMethod()
' Arrange
Dim order = New Order("Camera", 2)
Dim warehouse = Mock.Create(Of IWarehouse)()
Dim called As Boolean = False
Mock.Arrange(Function() warehouse.HasInventory("Camera", 2)).DoInstead(Sub() called = True)
' Act
order.Fill(warehouse)
' Assert
Assert.IsTrue(called)
End Sub
CallOriginal
In some cases you may want to arrange to call the original method implementation when it is called with a specific value and to call the mock with other values. For this you can use the CallOriginal method.
[TestMethod]
public void CallOriginal_TestMethod()
{
//Arrange
var order = Mock.Create<Order>(Behavior.CallOriginal, "Camera", 2);
Mock.Arrange(() => order.Receipt(DateTime.Today)).CallOriginal();
Mock.Arrange(() => order.Receipt(Arg.Matches<DateTime>(d => d > DateTime.Today))).Returns("Invalid DateTime");
//Act
var callWithToday = order.Receipt(DateTime.Today);
var callWithDifferentDay = order.Receipt(DateTime.Today.AddDays(1));
//Assert
Assert.AreEqual("Ordered 2 Camera on " + DateTime.Today.ToString("d"), callWithToday);
Assert.AreEqual("Invalid DateTime", callWithDifferentDay);
}
<TestMethod()>
Public Sub CallOriginal_TestMethod()
'Arrange
Dim order = Mock.Create(Of Order)(Behavior.CallOriginal, "Camera", 2)
Mock.Arrange(Function() order.Receipt(Arg.Matches(Of DateTime)(Function(d) d > DateTime.Today))).Returns("Invalid DateTime")
'Act
Dim callWithToday = order.Receipt(DateTime.Today)
Dim callWithDifferentDay = order.Receipt(DateTime.Today.AddDays(1))
'Assert
Assert.AreEqual("Ordered 2 Camera on " + DateTime.Today, callWithToday)
Assert.AreEqual("Invalid DateTime", callWithDifferentDay)
End Sub
In this example we arrange that when order.Receipt
method is called with argument DateTime.Today
, then the original method implementation should be called. But once the same method is called with a date later than DateTime.Today
then we return "Invalid date".
DoNothing
For arranging a void call it is a good practice to explicitly mark the mock with DoNothing. The method is basically syntactic sugar and does nothing, as the name suggests, but improves the readability of your code. Lets see it in practice.
Mock.ArrangeSet(() => warehouse.Manager = "John");
Mock.ArrangeSet(() => warehouse.Manager = "John").DoNothing();
Mock.ArrangeSet(Sub() warehouse.Manager = "John");
Mock.ArrangeSet(Sub() warehouse.Manager = "John").DoNothing();
The first and the second line are functionally the same, but specifying explicitly that setting this property returns nothing makes the code more readable.
Throws
The Throws method is used when you want to throw an exception for a particular method invocation. In the following example, we are throwing an invalid operation exception for trying to call warehouse.Remove with zero quantity.
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void Throws_TestMethod()
{
// Arrange
var order = new Order("Camera", 0);
var warehouse = Mock.Create<Iwarehouse>();
// Set up that the warehouse has inventory of any products with any quantities.
Mock.Arrange(() => warehouse.HasInventory(Arg.IsAny<string>(), Arg.IsAny<int>())).Returns(true);
// Set up that call to warehouse. Remove with zero quantity is invalid and throws an exception.
Mock.Arrange(() => warehouse.Remove(Arg.IsAny<string>(), Arg.Matches<int>(x => x == 0)))
.Throws(new InvalidOperationException());
// Act
order.Fill(warehouse);
}
<TestMethod()>
<ExpectedException(GetType(InvalidOperationException))>
Public Sub Throws_TestMethod()
' Arrange
Dim order = New Order("Camera", 0)
Dim warehouse = Mock.Create(Of IWarehouse)()
' Set up that the ware house has inventory of any products with any quantities.
Mock.Arrange(Function() warehouse.HasInventory(Arg.IsAny(Of String)(), Arg.IsAny(Of Integer)())).Returns(True)
' Set up that call to warehouse.Remove with zero quantity is invalid and throws an exception.
Mock.Arrange(Sub() warehouse.Remove(Arg.IsAny(Of String)(), Arg.Matches(Of Integer)(Function(x) x = 0))).Throws(New InvalidOperationException())
' Act
order.Fill(warehouse)
End Sub
In this case we use the ExpectedException
attribute from Microsoft.VisualStudio.TestTools.UnitTesting
to verify that exception of type InvalidOperationException
is thrown.
Matchers
Matchers let you ignore passing actual values as arguments used in mocks. Instead, they give you the possibility to pass just an expression that satisfies the argument type or expected value range. For example, if a method accepts string as a first parameter, you don’t need to pass a specific string like "Camera", instead you can use Arg.IsAny<string>()
.
There are 3 types of matchers supported in JustMock:
- Arg.IsAny<[Type]>();
- Arg.IsInRange([FromValue : int], [ToValue : int], [RangeKind])
- Arg.Matches
(Expression > expression)
Let's look at each one of them in details.
Arg.IsAny();
We already used this matcher in one of our examples above.
Mock.Arrange(() => warehouse.HasInventory(Arg.IsAny<string>(), Arg.IsAny<int>())).Returns(true);
Mock.Arrange(Function() warehouse.HasInventory(Arg.IsAny(Of String)(), Arg.IsAny(Of Integer)())).Returns(True)
This matcher specifies that when the HasInventory
method is called with any string as a first argument and any int as a second it should return true
.
Arg.IsInRange(int from, int to, RangeKind range)
The IsInRange matcher lets us arrange a call for an expected value range. With the RangeKind
argument we can specify whether the given range includes or excludes its boundaries.
For argument values ranging from 0 to 5, the following will return true
:
Mock.Arrange(() => foo.Echo(Arg.IsInRange(0, 5, RangeKind.Inclusive))).Returns(true);
Mock.Arrange(Function() foo.Echo(Arg.IsInRange(0, 5, RangeKind.Inclusive))).Returns(True)
For argument values ranging from 1 to 4, the following will return true
:
Mock.Arrange(() => foo.Echo(Arg.IsInRange(0, 5, RangeKind.Exclusive))).Returns(true);
Mock.Arrange(Function() foo.Echo(Arg.IsInRange(0, 5, RangeKind.Exclusive))).Returns(True)
Arg.Matches (Expression> expression)
This is the most flexible matcher and it allows you to specify your own matching expression. Let's illustrate it with a simple example:
Mock.Arrange(() => foo.Echo(Arg.Matches<int>( x => x < 10)).Returns(true);
Mock.Arrange(Function() foo.Echo(Arg.Matches(Of Integer)(Function(x) x < 10))).Returns(True)
With our expression (or predicate) x => x < 10
we specify that a call to foo.Echo
with an argument less than 10 should return true
.
For detailed information on the supported by JustMock matchers, visit Basic Usage | Matchers topic.
Properties
In the above examples we mock only methods, but you can also mock properties in the same way.
[TestMethod]
public void MockingProperties_TestMethod()
{
// Arrange
var warehouse = Mock.Create<Iwarehouse>();
Mock.Arrange(() => warehouse.Manager).Returns("John");
string manager = string.Empty;
// Act
manager = warehouse.Manager;
// Assert
Assert.AreEqual("John", manager);
}
<TestMethod()>
Public Sub MockingProperties_TestMethod()
' Arrange
Dim warehouse = Mock.Create(Of IWarehouse)()
Mock.Arrange(Function() warehouse.Manager).Returns("John")
Dim manager As String = String.Empty
' Act
manager = warehouse.Manager
' Assert
Assert.AreEqual("John", manager)
End Sub
Additionally, you can assert for property set.
[TestMethod]
[ExpectedException(typeof(StrictMockException))]
public void MockingProperties_PropertySet_TestMethod()
{
// Arrange
var warehouse = Mock.Create<Iwarehouse>(Behavior.Strict);
Mock.ArrangeSet(() => warehouse.Manager = "John");
// Act
warehouse.Manager = "Scott";
}
<TestMethod()> _
<ExpectedException(GetType(StrictMockException))> _
Public Sub MockingProperties_PropertySet_TestMethod()
' Arrange
Dim warehouse = Mock.Create(Of IWarehouse)(Behavior.[Strict])
Mock.ArrangeSet(Sub() warehouse.Manager = "John")
' Act
warehouse.Manager = "Scott"
End Sub
In the arrange step we set up that the warehouse manager can only be set to "John". But in the act step we set the manager to "Scott". That throws a mock exception. Have in mind that this will only work if you create your mock with StrictBehavior.
Another commonly used technique is to assert that setting a property to a specific value throws an exception. Let's arrange this:
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void MockingProperties_PropertySet_Throws_TestMethod()
{
// Arrange
var warehouse = Mock.Create<Iwarehouse>();
Mock.ArrangeSet(() => warehouse.Manager = "John").Throws<ArgumentException>();
// Act
// that's ok
warehouse.Manager = "Scott";
// but that would throw an ArgumentException
warehouse.Manager = "John";
}
<TestMethod()>
<ExpectedException(GetType(ArgumentException))> _
Public Sub MockingProperties_PropertySet_Throws_TestMethod()
' Arrange
Dim warehouse = Mock.Create(Of IWarehouse)()
Mock.ArrangeSet(Sub() warehouse.Manager = "John").Throws(Of ArgumentException)()
' Act
' that's ok
warehouse.Manager = "Scott"
' but that would throw an ArgumentException
warehouse.Manager = "John"
End Sub
Here we used the Throws
method discussed above to indicate that an exception should be thrown if the warehouse.Manager
is set to "John".
Events
The method Raises
allows you to raise an event when a method is called and to pass specific event arguments. Returning on our warehouse example, we may want to raise the ProductRemoved
event once the Remove
method is called.
[TestMethod]
public void RaisingAnEvent_TestMethod()
{
// Arrange
var warehouse = Mock.Create<Iwarehouse>();
Mock.Arrange(() => warehouse.Remove(Arg.IsAny<string>(), Arg.IsInRange(int.MinValue, int.MaxValue, RangeKind.Exclusive)))
.Raises(() => warehouse.ProductRemoved += null, "Camera", 2);
string productName = string.Empty;
int quantity = 0;
warehouse.ProductRemoved += (p, q) => { productName = p; quantity = q; };
// Act
warehouse.Remove(Arg.AnyString, Arg.AnyInt);
// Assert
Assert.AreEqual("Camera", productName);
Assert.AreEqual(2, quantity);
}
Remove
method is called we will raise the ProductRemoved
event with parameters "Camera" and 2.
Wrapper Of Framework Assert
In the examples in this topic we have used the framework assertion that is available when a reference to the Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll
is added to the project. In most of the examples in the further topics in the documentation a wrapper of the framework assert that exposes xUnit alike methods is used.
With the following using directive
using FrameworkAssert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
the wrapper is defined as:
public static class Assert
{
public static Exception Throws<T>( Action action ) where T : Exception
{
Exception targetException = null;
try
{
action();
FrameworkAssert.Fail( "No Expected " + typeof( T ).FullName + " was thrown" );
}
catch ( T ex )
{
targetException = ex;
}
return targetException;
}
public static void NotNull( object value )
{
FrameworkAssert.IsNotNull( value );
}
public static void Null( object value )
{
FrameworkAssert.IsNull( value );
}
public static void Equal<T>( T expected, T actual )
{
FrameworkAssert.AreEqual( expected, actual );
}
public static void NotEqual<T>( T notExpected, T actual )
{
FrameworkAssert.AreNotEqual( notExpected, actual );
}
public static void True( bool condition )
{
FrameworkAssert.IsTrue( condition );
}
public static void False( bool condition )
{
FrameworkAssert.IsFalse( condition );
}
public static void Same( object expected, object actual )
{
FrameworkAssert.AreSame( expected, actual );
}
}
Additionally, with JustMock installed you receive a sample project that uses this wrapper of the framework assertion. Refer to the project for further information on how to use the Assert
wrapper in your tests.