VSoft Technologies Blogs

rss

VSoft Technologies Blogs - posts about our products and software development.


Introducing Delphi Mocks


Delphi has had Unit Testing support (in the form of DUnit) for many years, but until now there very little in the way of Automatic Mocking. By contrast the .NET and Java worlds have plenty of mocking frameworks to choose from. 

So what are Automatic Mocks anyway? Simply put, they are mock objects that you don't have to hand code. What's wrong with hand coding mock objects you might ask? Well nothing, really, but consider the following example, where we pass an instance of our hand coded mock object to the object under test. 

type IFoo = interface
function Bar(param : integer) : string;
end;
procedure TTestSomething.SimpleTest;
var
something : ISomething;
mockFoo : IFoo;
begin
something := TSomething.Create;
mockFoo := TMockFoo.Create;
something.UseFoo(mockFoo);
end;


 What does our mockFoo mock object actually do, what is it's behavior, how many times will Bar be called and what will it return? It's not obvious from looking at the above code, so you would have to go off and find the TMockFoo

implementation. Not so hard, but what happens when someone else comes along and adds a new unit test that also uses TMockFoo, what happens if they change the TMockFoo implementation, will that break your existing tests. This is where automatic
mocks can help, because you define the behavior and expectations on you mock object right there in the unit test method :

procedure TTestSomething.SimpleTest;
var
something : ISomething;
mockFoo : TMock
begin
something := TSomething.Create;
//create our auto mock
mockFoo := TMock.Create;
//define the behavior of IFoo.Bar
mockFoo.Setup.WillReturn('hello something').When.Bar(1);
//define our expectations of how  many times TSomething will call IFoo.Bar
mockFoo.Setup.Expect.Once.When.Bar(1);
//now lets test ISomething.UseFoo
something.UseFoo(mockFoo);
//Now verify that ISomething used IFoo correctly
mockFoo.Verify;
end;

The auto mock object is defined right there in our unit test, and the behavior is not going to change unless we change it in our unit test. This sort of mocking makes it simple to focus our testing effort on TSomething rather than TFoo (our real IFoo implementation) or TMockFoo.

One of the reasons auto mocking hasn't really happened for Delphi is the lack of detailed and easy to use runtime type information (Rtti) and the lack of runtime code generation (yes it's always been possible, but not through a well defined interface like Reflection.Emit in .NET). Auto mocking involves creating types on the fly, in the case of interfaces, creating a type and implementing an interface. That's not an easy thing to do. There are some examples of this in Delphi's SOAP code, but they are not easy to follow. Delphi XE2 introduces some new features in the RTTI that make creating interface proxies simple. TVirtualInterface creates an implementation of an interface at runtime and marshals the method calls to the OnInvoke method. 

Delphi-Mocks - https://github.com/VSoftTechnologies/Delphi-Mocks is an attempt to create an Auto Mocking framework for Delphi. It makes use of Generics and Fluent style interfaces. Currently only interfaces can be mocked, and it supports only Delphi XE2 at this time. I hope to add support for TObject mocks and earlier versions (D2009+).





Comments are closed.
Showing 13 Comments
Avatar  Vincent Parrett 5 years ago

The code changed after this blog post to allow mocking interfaces and classes. The type is just called TMock now.

Avatar  r3code 5 years ago

Where did you get TInterfaceMock??
There is no TInterfaceMock in Delphi-Mocks sources.

Avatar  Cochran 6 years ago

I'm interested in seeing Delphi Mocks in Delphi 2009. Your reliance on RTTI.pas will make it a little more challenging but I think its possible. Portions of it rely on type information unavailable in 2009 but much of it just provides classes that make working with TypInfo easier. I'm working on using compiler conditionals from jedi.inc to implement enough of RTTI.pas to be useful in 2009. Perhaps it would fill in enough of the gaps for Delphi Mocks to be back ported. Of course the hard part is recreating TVirtualInterface for older versions, which you seem to have already started.

Avatar  Stefan Glienke 6 years ago

QC 98672 - type inference bug<br>QC 96343 - missing operator overloads on TValue<br><br>My repository is located at <a href="https://code.google.com/p/delphisorcery" rel="nofollow">code.google.com/p/delphisorcery</a> (also added it to the post, thanks for pointing that out)

Avatar  Vincent Parrett 6 years ago

Thanks Stefan. Those QC's do have me a bit worried, I'm wondering if they actually tested TValue????? Not much I can do about it though, the technique you used won't work because I used interfaces. I guess we'll just have to try and bring the QC's to the attention of the right people and hope they get fixed ;)<br><br>Your implementation is interesting, you combine the expectation and behavior into a single method chain. I initially did pretty much the same thing, but chose to split them out as I prefer to set the behavior and expectations separately. Fluent interfaces are nice, but for me they only work well when you are doing repetitive thing on the same object, like building up an html or xml document. <br><br>You Verification technique is quite different too, more like the Assert class in NUnit2 which is something else I have been working on ;) Interesting to see that the way we both handled the setup vs runtime mode for the fluent interface is pretty much the same, I couldn't think of any other way to do it.. I guess you found that too. <br><br>I just had a look at how you handle comparing TValues.. like me you created an Equals helper method.. I did worry that perhaps my implementation was too naive, I started doing something similar to your implementation but kept getting failures, resorted to comparing ToString() values and that seems to work. After seeing your code I'm back to worrying about my implementation!

Avatar  Vincent Parrett 6 years ago

Hmm.. blog swallowed some brackets, should be IMyInterface LT T GT (still can't the blog to post my brackets!) I just had a look to see if I could add overloads that allow specifying the return type but interfaces don't allow parameterized methods :(I guess I could always use some record helpers for those issues as they occur or users can just pass in TValue.From..

Avatar  Vincent Parrett 6 years ago

Hmm.. do you have the QC number handy. I did wonder how well TValue would work, but so far in the testing I did with it, TValue seemed to work pretty well. I have just pushed my changes from today.. merged the mocks into one TMock record and re-organised the proxy classes.. was surpisingly easy to get it working with TVirtualMethodInterceptor. The hardest part was figuring out the path to the method's implementing class.. crashed the debugger a few times before I got it right ;) The doco for this stuff still leaves a lot to be desired. <br><br>I couldn't find a link to your code on your blog, or am I blind? The link colors on the page are hard to diffentiate. Searching for DSharp Mock didn't turn up anything either. <br><br>Interfaces on generic interfaces (IMyInterface) can cause problems if you implement the interface more than once on a class (with different types T of course) as both implementations get the same guid.. I've wasted hours on that problem before I read about that somewhere!<br><br>There is still more that can be done with the mocking stuff, for example what about mocking multiple interfaces on the same mock, or recursive mocking (where a method returns another interface). I would also like to look at integration with DI containers like Delphi Spring.

Avatar  Stefan Glienke 6 years ago

Yes, and I also published a blog post about it yesterday ;)<br><br><a href="http://delphisorcery.blogspot.com/2011/09/pimp-your-unit-tests-using-mock-objects.html" rel="nofollow">delphisorcery.blogspot.com/2011/09/pimp-your-unit-tests-using-mock-objects.html</a><br><br>Our implementations are in fact very similar. Mine's a bit more minimalistic regarding some method overloads and I put interface and object into one Mock type.<br><br>Also I found some personal pros and cons ;)<br>You are using TValue and its implicit operators for WillReturn. I made some QC about some missing operator overloads so I did not use this and implemented a generic method on the record which wraps up the value and passes it into the interface method (need generic methods on interfaces please!). You can use type inference at that point but it is bugged in some cases. But at least you don'T have to put Rtti into the uses in your unit test if you have some return value that does not have some implicit operator overload.<br><br>What I like and did not know is that the interface does not need to have a guid (will fix that later in my implementation *g*)

Avatar  Vincent Parrett 6 years ago

Got it.. have to look at the TRttiMethod passed in to the OnBefore event of TVirtualMethodInteceptor and ignore it if its implementation is in TObject. Seems to work well. Just pushed my changes to github, changed TInterfaceMock to TMock but the rest all just works the same.

Avatar  Vincent Parrett 6 years ago

LOL! Well I guess that just validates the idea, that Delphi needed a mocking framework. Did you publish your mocking framework? I have almost got TObject mocking working, just need to figure out how to filter the methods to stop it calling methods in the destruction chain (like BeforeDestruction). I'll update github later today.

Avatar  Stefan Glienke 6 years ago

Looks like we both got the same idea. ;)

Avatar  Vincent Parrett 6 years ago

Thanks Nick. I've been wanting to create a mocking framework for delphi for a while now, TVirtualInterface made it easy. The framework is still a work in progress though, I tackled interface mocking first as I use interfaces a lot, but it should be possible to us the virtual method intercepter stuff to create object mocks, I'll work on that next, then look at how to do it for older versions of Delphi. Let me know if you have any ideas/suggestions for improvement.

Avatar  Nick Hodges 6 years ago

Argh! You beat me to it. Just yesterday I was sitting at the Delphi World Tour event in Washington, DC checking out TVirtualInterface and realized that I could very easily create a mock library with it. Was going to start today. Glad I saw your post before I spent my Sunday duplicating your excellent work. ;-)