|
|
       
This topic will guide you through couple of 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 useful in the
most common testing scenarios.
To use Telerik JustMock in your
test projects you first need to add a reference to the Telerik.JustMock.dll assembly
(Telerik.JustMock.Silverlight.dll in case of a Silverlight project).
This assembly is located in the folder you installed
Telerik JustMock under Libraries (by default
C:\Program Files\Telerik\JustMock\Libraries\).
Remember to include the Telerik.JustMock namespace in the test file.
To illustrate the use of Telerik JustMock
in the next examples we will use a sample warehouse and a dependant 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:
CopyC# 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"));
}
} CopyVB 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 ClassTo execute the tests we use a Test Project inside Visual Studio 2010 (you can use an earlier version of Visual Studio as well). Arrange / Act / Assert
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
Until now we saw how to create mock instance with Mock.Create<>, how to arrange with Mock.Arrange and how to assert with Mock.Assert. We also used two methods - Returns and MustBeCalled in the arrange step. There are a number of handy methods we can use to make our 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. Let's use the example from above to illustrate to use of DoInstead.
CopyC# [TestMethod]
public void DoInstead_TestMethod()
{
var warehouse = Mock.Create<Iwarehouse>();
var order = new Order("Camera", 2);
bool called = false;
Mock.Arrange(() => warehouse.HasInventory("Camera", 2)).DoInstead(() => called = true);
order.Fill(warehouse);
Assert.IsTrue(called);
} CopyVB <TestMethod()>
Public Sub DoInstead_TestMethod()
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)
order.Fill(warehouse)
Assert.IsTrue(called)
End Sub
Let's put it simple – 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 method.
Read more about DoInstead. 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 we can use the CallOriginal method.
CopyC# [TestMethod]
public void CallOriginal_TestMethod()
{
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");
var callWithToday = order.Receipt(DateTime.Today);
var callWithDifferentDay = order.Receipt(DateTime.Today.AddDays(1));
Assert.AreEqual("Ordered 2 Camera on " + DateTime.Today.ToString("d"), callWithToday);
Assert.AreEqual("Invalid DateTime", callWithDifferentDay);
} CopyVB <TestMethod()>
Public Sub CallOriginal_TestMethod()
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")
Dim callWithToday = order.Receipt(DateTime.Today)
Dim callWithDifferentDay = order.Receipt(DateTime.Today.AddDays(1))
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".
DoNothingFor arranging a void call it is a good practice to explicitly mark the mock with DoNothing. Basically it is just a syntactic sugar and does nothing, as the name suggests, but improves the readability of your code. Lets see it in practice. CopyC# Mock.ArrangeSet(() => warehouse.Manager = "John");
Mock.ArrangeSet(() => warehouse.Manager = "John").DoNothing(); CopyVB 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.
CopyC# [TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void Throws_TestMethod()
{
var order = new Order("Camera", 0);
var warehouse = Mock.Create<Iwarehouse>();
Mock.Arrange(() => warehouse.HasInventory(Arg.IsAny<string>(), Arg.IsAny<int>())).Returns(true);
Mock.Arrange(() => warehouse.Remove(Arg.IsAny<string>(), Arg.Matches<int>(x => x == 0)))
.Throws(new InvalidOperationException());
order.Fill(warehouse);
} CopyVB <TestMethod()>
<ExpectedException(GetType(InvalidOperationException))>
Public Sub Throws_TestMethod()
Dim order = New Order("Camera", 0)
Dim warehouse = Mock.Create(Of IWarehouse)()
Mock.Arrange(Function() warehouse.HasInventory(Arg.IsAny(Of String)(), Arg.IsAny(Of Integer)())).Returns(True)
Mock.Arrange(Sub() warehouse.Remove(Arg.IsAny(Of String)(), Arg.Matches(Of Integer)(Function(x) x = 0))).Throws(New InvalidOperationException())
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 Telerik JustMock:
- Arg.IsAny<[Type]>();
-
Arg.IsInRange([FromValue : int], [ToValue : int], [RangeKind])
- Arg.Matches<T>(Expression<Predicate<T>> expression)
Let's look at each one of them in details. Arg.IsAny<T>();We already used this matcher in one of our examples above. | C# | Copy |
|---|
Mock.Arrange(() => warehouse.HasInventory(Arg.IsAny<string>(), Arg.IsAny<int>())).Returns(true);
|
| Visual Basic | Copy |
|---|
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 let 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: | C# | Copy |
|---|
Mock.Arrange(() => foo.Echo(Arg.IsInRange(0, 5, RangeKind.Inclusive))).Returns(true);
|
| Visual Basic | Copy |
|---|
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: | C# | Copy |
|---|
Mock.Arrange(() => foo.Echo(Arg.IsInRange(0, 5, RangeKind.Exclusive))).Returns(true);
|
| Visual Basic | Copy |
|---|
Mock.Arrange(Function() foo.Echo(Arg.IsInRange(0, 5, RangeKind.Exclusive))).Returns(True)
|
Arg.Matches<T> (Expression<Predicate<T>> 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: | C# | Copy |
|---|
Mock.Arrange(() => foo.Echo(Arg.Matches<int>( x => x < 10)).Returns(true);
|
| Visual Basic | Copy |
|---|
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.
Properties
In the above examples we mock only methods, but you can also mock properties in the same way. CopyC# [TestMethod]
public void MockingProperties_TestMethod()
{
var warehouse = Mock.Create<Iwarehouse>();
Mock.Arrange(() => warehouse.Manager).Returns("John");
string manager = string.Empty;
manager = warehouse.Manager;
Assert.AreEqual("John", manager);
} CopyVB <TestMethod()>
Public Sub MockingProperties_TestMethod()
Dim warehouse = Mock.Create(Of IWarehouse)()
Mock.Arrange(Function() warehouse.Manager).Returns("John")
Dim manager As String = String.Empty
manager = warehouse.Manager
Assert.AreEqual("John", manager)
End SubAdditionally we can also have some more fun with properties. For example, we can assert for property set. CopyC# [TestMethod]
[ExpectedException(typeof(MockException))]
public void MockingProperties_PropertySet_TestMethod()
{
var warehouse = Mock.Create<Iwarehouse>(Behavior.Strict);
Mock.ArrangeSet(() => warehouse.Manager = "John");
warehouse.Manager = "Scott";
} CopyVB <TestMethod()> _
<ExpectedException(GetType(MockException))> _
Public Sub MockingProperties_PropertySet_TestMethod()
Dim warehouse = Mock.Create(Of IWarehouse)(Behavior.[Strict])
Mock.ArrangeSet(Sub() warehouse.Manager = "John")
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: CopyC# [TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void MockingProperties_PropertySet_Throws_TestMethod()
{
var warehouse = Mock.Create<Iwarehouse>();
Mock.ArrangeSet(() => warehouse.Manager = "John").Throws<ArgumentException>();
warehouse.Manager = "Scott";
warehouse.Manager = "John";
} CopyVB <TestMethod()> _
<ExpectedException(GetType(ArgumentException))> _
Public Sub MockingProperties_PropertySet_Throws_TestMethod()
Dim warehouse = Mock.Create(Of IWarehouse)()
Mock.ArrangeSet(Sub() warehouse.Manager = "John").Throws(Of ArgumentException)()
warehouse.Manager = "Scott"
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.
CopyC# [TestMethod]
public void RaisingAnEvent_TestMethod()
{
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; };
warehouse.Remove(Arg.AnyString, Arg.AnyInt);
Assert.AreEqual("Camera", productName);
Assert.AreEqual(2, quantity);
}
Here in the arrange step we set up that once the warehouse’s 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 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: | C# | Copy |
|---|
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 );
}
}
|
CopyC# 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 Telerik 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.
See Also
|