For German Version see Mocking with Spring4D
In this article we will take a look at mocking with Spring4D. For some basics about mocking please look at the first article in this series here.
Spring4D
Spring4D is an open source framework for Delphi that offers a lot of additional features besides the mocking classes included since version 1.2. One of my favorites here are the generic interfaces for lists, collections and dictionaries or the multicast events. A „small“ ORM mapper and a cool dependency injection framework are also included.
To install, simply download the archive of the desired version from the repository, unpack the archive into a folder that you normally use for your components, and run Build.exe in the root folder of the unpacked directory to compile the framework. If you select the checkbox „Update Registry“, Build.exe will also add the necessary entries for Spring4D to your library path. Components are not registered by Spring4D, the classes of the framework are only used in the code.
Starting with Spring4D Mocks
To work with the mocks, you only need the Spring.Mocking unit in the uses statement. The unit contains most of the required structures. The core of the framework is a generic Record Mock<T>, which you instantiate for the interfaces or classes to be replaced. Some of the implicit typecasts for this record are overloaded, so that the record can be used wherever the class or interface you want to replace is needed. On the other hand you can also use generic utility interfaces for mocking like ISetup<T> or IMock<T> thanks to the overloaded operators.
In the previous article we had written first tests for the method Calculate of the class TShoppingBasket. The method Calculate calculates the value of a shopping basket from the sum of the contents minus the values of the deposited discount codes. Since this method uses one or more interfaces we had problems to test it in isolation. The interface ILogger for log output and the interface IVoucherCalculator to calculate the values of coupon codes. The implementations for both interfaces are given in the TShoppingbasket constructor.
TShoppingBasket = class(TObject)
strict private
FItemsCalculator: IVoucherCalculator;
...
public
constructor Create(const logger: ILogger; const customerId: string;
const voucherCalculator: IVoucherCalculator);
procedure AddVoucher(const code: string);
procedure AddVouchers(const codes: TStringDynArray);
...
procedure Calculate;
property TotalValue: Currency read FTotalValue;
property TotalDiscount : Currency read FTotalDiscount;
property Hints: TStrings read FHints;
end;
At first we replaced the used interface ILogger just with a simple stub to be able to test the calculation of simple shopping baskets without discount coupons. Since ILogger is only called without providing values, we could supply this interface for the test simply by a zero-object logger. For IVoucherCalculator this makes no sense. The interface should return values for vouchers that are as flexible as possible, so a stub, that would return one or more fixed values, wouldn’t help us much.
On the other hand, a productive implementation doesn’t help us here either. We don’t want to test here whether an implementation of IVoucherCalculator actually works and whether, for example, coupons used twice lead to errors or whether validity and value are determined correctly. Apart from that, a productive implementation of the interface will almost certainly have to use external resources again to realize the functions.
With help of Mock<T> we can solve our problem. Wir just include Spring.Mocking in our uses clause and define another field FVoucherMock for the mock in our test class:
strict private
FShoppingBasket: TShoppingBasket;
FVoucherMock: Mock<IVoucherCalculator>;
...
Thanks to the overloaded operators for implicit typecasts, we can pass the record in FVoucherMock directly to the constructor of our SUT (System under test) in the setup method of our test:
begin
FShoppingBasket := TShoppingBasket.Create(TNullLogger.Create, '', FVoucherMock);
end;
In the test itself, the mock is similarly easy to use. In Spring4D the interfaces and calls are designed in such a way that a simple setup of the mock can later be realized as a one-liner via the Fluent syntax.
But at fist let us go back to the declaration of the interface we want to replace. The interface has only one method, that is being called by TShoppingBasket it is: CalculateVoucher.
EInvalidVoucher = class(ELogicalException);
/// <summary>interface to validate an calculate the code of a voucher</summary>
IVoucherCalculator = interface(IInvokable)
/// <summary>validates a voucher</summary>
/// <returns>Value of given voucher code</returns>
/// <param name="itemsvalue">value of items in basket</param>
/// <param name="customerId">Unique Id for customer</param>
/// <param name="voucher">code for a voucher</param>
/// <exception cref="EInvalidVoucher">is raised if given voucher is invalid (code already used or invalid code etc)</exception>
/// <exception cref="ETechnicalException">are raised when calculation fails due to internal errors</exception>
function CalculateVoucher(const itemsvalue: Currency; const customerId, voucher: string): Currency;
end;
Setting up the mock
In a first test for the Calculate method we can first consider the simple case that a single voucher code is stored. We would add a sample and a voucher to the shopping cart, similar to other tests.
We also have to instruct our mock what value it should return for this test. We do this via the Setup property of the mock record. With the input parameter of the generic method Returns we specify which value the mock should return at the call – in this case the value of the constant voucherValue.
const
voucherCode = 'XDRET13';
voucherValue = 20;
singleItem : TBasketItem = (Count: 1; Price: 200;);
begin
// Arrange
FShoppingBasket.AddItem(SingleItem);
FShoppingBasket.AddVoucher(voucherCode);
FVoucherMock.Setup.Returns<Currency>(voucherValue).When.CalculateVoucher(SingleItem.Price,'',voucherCode);
// Act
FShoppingBasket.Calculate;
// Assert
CheckEquals(sampleValue, FShoppingBasket.TotalDiscount);
end;
Practically the method Returns also returns an interface IWhen<T> with which we can use to define the conditions under which the specified value should be returned. The interface IWhen is clear and has only one method called „When“ which returns the current type T of the mock. This allows the code completion of Delphi to help us with the call.
By calling the methods of the IVoucherCalculator interface in the mock setup with certain parameters, we define the conditions under which the method Returns<T> actually returns the input parameter. If the mock is called later by the SUT with other values of the CalculateVoucher parameters than those we specified, the mock will return default values instead. So we don’t have to set up every call beforehand, but only do this „effort“ when we need certain return values. Conversely, we can also define different return values for different input values by several calls to Setup, if this is required for the test.
Checking behavoir
The test implemented by the Test_OneVoucher method of our test case shown above works. The value we test in TotalDiscount meets our expectations. Theoretically, a successful test could also be the result of an incorrect implementation. So far, the test only checks the state after execution of our test object. What we don’t check in the test yet is whether our mock was called at all and was called with the parameters as we expected. In other words we havent’t checked yet whether our implementation behaves right.
Other mocking frameworks usually offer a method Verify for the mock, which checks if all methods from the mock setup were called. The Mock in Spring4D does not know this method and follows a slightly different approach.
In Spring4D we can achieve behavioral testing in two ways. On the one hand, we can change the basic behavior of our mock on calls and on the other hand we can explicitly check the received calls.
MockBehavoir
If we don’t specify anything else, Spring4D creates a mock in dynamic mode. This means that the mock accepts all types of calls with any parameter. If a behavior for certain calls and parameter values is defined in the setup, it will be executed. If nothing is specifically defined, the mock executes standard behavior. If return values are expected from the call, the mock returns default values – which means for Spring4D: for simple data types 0, false or empty string is returned but for interfaces the framework will return annother mock of this interface and not nil.
The mock can also be switched to strict mode. In this mode, the mock throws an exception immediately if a call is made that was not previously defined in Setup. The behavior can be changed at any time via the Behavoir property of the mock. We could achieve our behavior check by this small extension of the test method shown above
...
// Arrange
...
FVoucherMock.Behavior := TMockBehavior.Strict;
FVoucherMock.Setup.Returns<Currency>(voucherValue).When.CalculateVoucher(SingleItem.Price,'',voucherCode);
// Act
FShoppingBasket.Calculate;
// Assert
CheckEquals(voucherValue, FShoppingBasket.TotalDiscount);
end;
If the Strict-Behavoir is needed for multiple tests, the mock record also provides a static initialization method named Create which could be used in the test setup.
FVoucherMock := Mock<IVoucherCalculator>.Create(TMockBehavior.Strict);
Using method Received
An alternative to setting the mock behavior to strict is to use the Received function of the mock. The function returns the type T for which the mock was instantiated, similar to the function When in the setup. Received is called the same way When is called. You define your expectation for the function call by calling it with the specified function parameters on the return value of Received. In addition, Received can also pass the expected number of calls with the optional Times type parameter. Using Received, our behavior check then looks like this:
...
// Arrange
...
FVoucherMock.Setup.Returns<Currency>(voucherValue).When.CalculateVoucher(SingleItem.Price,'',voucherCode);
// Act
FShoppingBasket.Calculate;
// Assert
FVoucherMock.Received(Times.Once).CalculateVoucher(SingleItem.Price, '', voucherCode);
CheckEquals(voucherValue, FShoppingBasket.TotalDiscount);
end;
Received thus comes quite close to the behavior of Verify. The decisive difference is that in Spring4D you have the possibility to carry out the setup and test independently of each other and to decide for each case how special or general the test should be.
This article should give you a first overview about mocking with Spring4D. In the following article we will look at how we deal with typecasts and exceptions, how we formulate parameter values more generally or how we check call sequences.