We recently updated Delphi Mocks to allow for better parameter matching on Expectations registered with the Mock. This allows the developer to place tighter controls on verifying that a mocked interface/object method is called.
Below is a simple example of when the parameter matchers can be used.
procedure TExample_InterfaceImplementTests.Implement_Multiple_Interfaces;
var
sutProjectSaver : IProjectSaveCheck;
mockProject : TMock<IProject>;
begin
//Test that when we check and save a project, and its dirty, we save.
//CREATE - The project saver under test.
sutProjectSaver := TProjectSaveCheck.Create;
//CREATE - Mock project to control our testing.
mockProject := TMock<IProject>.Create;
//SETUP - Mock project will show as dirty and will expect to be saved.
mockProject.Setup.WillReturn(true).When.IsDirty;
//NEW! - Add expectation that the save will be called as dirty is returning true.
// As we don't care about the filename value passed to us we
// allow any string to be passed to report this expectation as met.
mockProject.Setup.Expect.Once.When.Save(It(0).IsAny<string>());
//TEST - Visit the mock element to see if our test works.
sutProjectSaver.Execute(mockProject);
//VERIFY - Make sure that save was indeed called.
mockProject.VerifyAll;
end;
Previously the developer writing this test would have to provide the exact filename to be passed to the mocked Save method. As we don't know what the projects filename is going to be (in our example case), we would either have to;
1. Forgo doing this test.
2. Implement a project object to test with.
Both of these options are not ideal.
Parameter matchers resolve this situation. It is now simple to either restrict or broaden the parameters passed to mocked methods that will satisfy the expectation defined. To achieve this Delphi-Mocks offers eleven new functions;
function It(const AParamIndx : Integer) : ItRec;
function It0 : ItRec;
function It1 : ItRec;
function It2 : ItRec;
function It3 : ItRec;
function It4 : ItRec;
function It5 : ItRec;
function It6 : ItRec;
function It7 : ItRec;
function It8 : ItRec;
function It9 : ItRec;
The first "function It(const AParamIndx : Integer) : ItRec;" allows the developer to specify the index of the parameter they wish to set for the next expectation setup of a mock method. It(0) will refer to the first parameter, It(1) the second and so forth. Note that the reason for specifying the parameter index is that Delphi's parameter evaluation order is not defined, so we could not rely on the parameters being evaluated in order (which is what we did when we initially wrote this feature). Interestingly, with the 64 bit Delphi compiler, parameter evaluation does appear to happen in order, but we could not be certain this will always be the case.
The other ten functions It0 through to It9 are simply wrappers of the index call passing the index in their name. All these functions return an ItRec. The ItRec has the function structure;
ItRec = record
var
ParamIndex : cardinal;
constructor Create(const AParamIndex : Integer);
function IsAny<T>() : T ;
function Matches<T>(const predicate: TPredicate<T>) : T;
function IsNotNil<T> : T;
function IsEqualTo<T>(const value : T) : T;
function IsInRange<T>(const fromValue : T; const toValue : T) : T;
function IsIn<T>(const values : TArray<T>) : T; overload;
function IsIn<T>(const values : IEnumerable<T>) : T; overload;
function IsNotIn<T>(const values : TArray<T>) : T; overload;
function IsNotIn<T>(const values : IEnumerable<T>) : T; overload;
{$IFDEF SUPPORTS_REGEX} //XE2 or later
function IsRegex(const regex : string; const options : TRegExOptions = []) : string;
{$ENDIF}
end;
Each of the functions creates a different matcher. For example the IsAny<T> will cause the expectation to be met when the parameter passed to the mock is of any value that has the type T. In the example above this type would be a string. You will also notice that each function returns the type T. This is so that each call can be placed within the mock methods call directly. Doing so helps with making sure parameter types match the testing value.
IsEqualTo<T> requires that the parameter matches exactly to the value passed into the IsEqualTo<T>. This could be used to restrict the expectation to a tighter test of the functionality under test.
//Match on the filename being "temp.txt" only.
mockProject.Setup.Expect.Once.When.Save(It(0).IsEqualTo<string>('temp.txt'));
//VERIFY - Make sure that save was indeed called.
mockProject.VerifyAll;
In the future we are looking to provide “And”\”Or” operators. These operators might also live on the ItRec and allow combining with as many other matchers using the same type.
//Match on the filename being "temp.txt" or "temp.doc" only.
mockProject.Setup.Expect.Once.When.Save(
It(0).Or(It(0).IsEqualTo<string>('temp.txt'),
It(0).IsEqualTo<string>('temp.doc'));
//VERIFY - Make sure that save was indeed called.
mockProject.VerifyAll;
There might be a better way to make the resulting code a bit cleaner. It would make the tests easier to read, instead of using regex which is also possible in this case. As a result we believe this would be a good edition to the library.
Feel free to clone the repository from GitHub. If you have some time to spare submit a pull requests or two with your ideas/improvements. We believe this is a great little project worthy of some attention. Let us know what you think of the changes so far.