1 module mocked.mocker;
2 
3 import mocked.error;
4 import mocked.meta;
5 import mocked.option;
6 import std.algorithm;
7 import std.array;
8 import std.container.dlist;
9 import std.conv;
10 import std.exception;
11 import std.format : format;
12 import std.meta;
13 import std.range : drop;
14 import std.traits;
15 import std.typecons;
16 
17 /**
18  * Used to save heterogeneous repositories in a single container and verify
19  * their expectations at the end.
20  */
21 interface Verifiable
22 {
23     /**
24      * Verifies that certain expectation requirements were satisfied.
25      *
26      * Throws: $(D_PSYMBOL ExpectationViolationException) if those issues occur.
27      */
28     void verify();
29 }
30 
31 private template findMatchIndex(Options, Call, ExpectationTuple, Arguments...)
32 {
33     alias Pair = Tuple!(size_t, "index", bool, "matched");
34     alias T = Nullable!Pair;
35 
36     /*
37      * This function tries to find the best match for a function with the given
38      * arguments. If the expectations are ordered, it can modify the
39      * expectation chain; it will remove .repeatAny() calls if it can find a
40      * better match after them.
41      */
42     T findMatchIndex(ref Call[] calls,
43             ref ExpectationTuple expectationTuple, ref Arguments arguments,
44             size_t startIndex)
45     {
46         auto withComparedArguments = calls
47             .drop(startIndex)
48             .map!(call => tuple(call, call.compareArguments!Options(arguments)));
49 
50         const relativeIndex = withComparedArguments
51             .countUntil!(call => expectationTuple.ordered || call[1]);
52         if (relativeIndex == -1)
53         {
54             return T();
55         }
56         const size_t absoluteIndex = startIndex + relativeIndex;
57         auto matchAtIndex = nullable(Pair(absoluteIndex, withComparedArguments[relativeIndex][1]));
58         if (calls[absoluteIndex].repeat_ != 0)
59         {
60             return matchAtIndex;
61         }
62 
63         const secondTry = findMatchIndex!(Options, Call, ExpectationTuple, Arguments)(
64                 calls, expectationTuple, arguments, absoluteIndex + 1);
65 
66         if (secondTry.isNull || !secondTry.get.matched)
67         {
68             return matchAtIndex;
69         }
70         else if (expectationTuple.ordered)
71         {
72             ++expectationTuple.actualCall;
73             calls = calls.remove(absoluteIndex);
74 
75             return T(Pair(secondTry.get.index - 1, secondTry.get.matched));
76         }
77         else
78         {
79             return secondTry;
80         }
81     }
82 }
83 
84 private enum string mockCode = q{
85     auto expectationTuple = getExpectationTuple(expectationSetup);
86     auto overloads = &expectationTuple.methods[j].overloads[i];
87 
88     const matchAtIndex = findMatchIndex!Options(overloads.calls,
89             expectationTuple, arguments, 0);
90 
91     if (matchAtIndex.isNull)
92     {
93         throw unexpectedCallError!(typeof(super), Overload.ParameterTypes)(expectation.name, arguments);
94     }
95     expectationTuple.actualCall += matchAtIndex.get.index;
96     auto matchedElement = &overloads.calls[matchAtIndex.get.index];
97 
98     if (matchedElement.repeat_ > 0 && !matchAtIndex.get.matched)
99     {
100         auto overloadArguments = matchedElement.arguments;
101 
102         overloads.clear();
103 
104         throw unexpectedArgumentError!(typeof(super),
105                 Overload.ParameterTypes, Overload.Arguments)(
106                 expectation.name, arguments, overloadArguments);
107     }
108 
109     if (expectationTuple.ordered
110             && matchedElement.repeat_ == 1
111             && matchedElement.index != ++expectationTuple.actualCall)
112     {
113         throw outOfOrderCallError!(typeof(super), Overload.ParameterTypes)(
114                     expectation.name, arguments,
115                     matchedElement.index,
116                     expectationTuple.actualCall);
117     }
118 
119     scope(exit)
120     {
121         if (matchedElement.repeat_ > 1)
122         {
123             --matchedElement.repeat_;
124         }
125         else if (matchedElement.repeat_ == 1)
126         {
127             overloads.calls = overloads.calls.remove(matchAtIndex.get.index);
128         }
129     }
130 
131     static if (is(Overload.Return == void))
132     {
133         if (matchedElement.action_ !is null)
134         {
135             matchedElement.action_(arguments);
136         }
137     }
138     else static if (is(T == interface))
139     {
140         Overload.Return ret = matchedElement.action_ is null
141             ? matchedElement.return_
142             : matchedElement.action_(arguments);
143     }
144     else
145     {
146         Overload.Return ret = matchedElement.passThrough_
147             ? __traits(getMember, super, expectation.name)(arguments)
148             : matchedElement.action_ is null
149             ? matchedElement.return_
150             : matchedElement.action_(arguments);
151     }
152 
153     static if (!is(T == interface) && is(Overload.Return == void))
154     {
155         if (matchedElement.passThrough_)
156         {
157             __traits(getMember, super, expectation.name)(arguments);
158         }
159     }
160 
161     static if (![Overload.qualifiers].canFind("nothrow"))
162     {
163         if (matchedElement.exception !is null)
164         {
165             throw matchedElement.exception;
166         }
167     }
168     static if (!is(Overload.Return == void))
169     {
170         return ret;
171     }
172 };
173 
174 private auto getExpectationTuple(T)(ref T expectationSetup) @trusted
175 {
176     alias R = typeof(cast() typeof(expectationSetup.expectationTuple).init)*;
177 
178     return cast(R) &expectationSetup.expectationTuple;
179 }
180 
181 private enum string stubCode = q{
182     auto overloads = getExpectationTuple(expectationSetup)
183         .methods[j].overloads[i];
184     auto match = overloads.find!(call => call.compareArguments!Options(arguments));
185     // Continue to search for better matches
186     for (auto preliminaryMatch = match; !preliminaryMatch.empty; preliminaryMatch.popFront)
187     {
188         if (!preliminaryMatch.front.arguments.isNull
189                 && preliminaryMatch.front.compareArguments!Options(arguments))
190         {
191             match = preliminaryMatch;
192             break;
193         }
194     }
195     if (match.empty)
196     {
197         throw unexpectedCallError!(typeof(super), Overload.ParameterTypes)(expectation.name, arguments);
198     }
199 
200     static if (is(Overload.Return == void))
201     {
202         if (match.front.action_ !is null)
203         {
204             match.front.action_(arguments);
205         }
206     }
207     else static if (is(T == interface))
208     {
209         Overload.Return ret = match.front.action_ is null
210             ? match.front.return_
211             : match.front.action_(arguments);
212     }
213     else
214     {
215         Overload.Return ret = match.front.passThrough_
216             ? __traits(getMember, super, expectation.name)(arguments)
217             : match.front.action_ is null
218             ? match.front.return_
219             : match.front.action_(arguments);
220     }
221 
222     static if (!is(T == interface) && is(Overload.Return == void))
223     {
224         if (match.front.passThrough_)
225         {
226             __traits(getMember, super, expectation.name)(arguments);
227         }
228     }
229 
230     static if (![Overload.qualifiers].canFind("nothrow"))
231     {
232         if (match.front.exception !is null)
233         {
234             throw match.front.exception;
235         }
236     }
237     static if (!is(Overload.Return == void))
238     {
239         return ret;
240     }
241 };
242 
243 /**
244  * Mocker instance with default options.
245  *
246  * See_Also: $(D_PSYMBOL Factory), $(D_PSYMBOL configure).
247  */
248 alias Mocker = Factory!(Options!());
249 
250 /**
251  * Constructs a mocker with options passed as $(D_PARAM Args).
252  *
253  * Params:
254  *     Args = Mocker options.
255  *
256  * See_Also: $(D_PSYMBOL Factory), $(D_PSYMBOL Mocker).
257  */
258 auto configure(Args...)()
259 {
260     return Factory!(Options!Args)();
261 }
262 
263 /// ditto
264 alias Configure(Args...) = Factory!(Options!(staticMap!(Comparator, Args)));
265 
266 /**
267  * A class through which one creates mock objects and manages expectations
268  * about calls to their methods.
269  *
270  * Params:
271  *     Options = Mocker $(D_PSYMBOL Options).
272  *
273  * See_Also: $(D_PSYMBOL Mocker), $(D_PSYMBOL CustomMocker).
274  */
275 struct Factory(Options)
276 {
277     private DList!Verifiable repositories;
278 
279     /**
280      * Mocks the type $(D_PARAM T).
281      *
282      * Params:
283      *     T = The type to mock.
284      *     Args = Constructor parameter types.
285      *     args = Constructor arguments.
286      *
287      * Returns: A mock builder.
288      */
289     auto mock(T, Args...)(Args args)
290     {
291         mixin NestedMock!(Repository!T.Mock, Options, Args);
292 
293         auto mock = new Mock(args);
294         auto mocked = new Mocked!T(mock, mock.expectationSetup);
295 
296         this.repositories.insertBack(mocked);
297         return mocked;
298     }
299 
300     /**
301      * Stubs the type $(D_PARAM T).
302      *
303      * Params:
304      *     T = The type to stub.
305      *     Args = Constructor parameter types.
306      *     args = Constructor arguments.
307      *
308      * Returns: A stub builder.
309      */
310     auto stub(T, Args...)(Args args)
311     if (isPolymorphicType!T)
312     {
313         mixin NestedMock!(Repository!T.Stub, Options, Args);
314 
315         auto stub = new Mock(args);
316 
317         return new Stubbed!T(stub, stub.expectationSetup);
318     }
319 
320     /**
321      * Verifies that certain expectation requirements were satisfied.
322      *
323      * Throws: $(D_PSYMBOL ExpectationViolationException) if those issues occur.
324      */
325     void verify()
326     {
327         this.repositories.each!(repository => repository.verify);
328     }
329 
330     ~this()
331     {
332         verify;
333     }
334 }
335 
336 /**
337  * Mock builder.
338  *
339  * Params:
340  *     T = Mocked type.
341  */
342 final class Mocked(T) : Builder!T, Verifiable
343 {
344     private Repository!T.Mock* repository;
345 
346     /**
347      * Params:
348      *     mock = Mocked object.
349      *     repository = Mock repository.
350      */
351     this(T mock, ref Repository!T.Mock repository)
352     in (mock !is null)
353     {
354         super(mock);
355         this.repository = &repository;
356     }
357 
358     /**
359      * Returns: Repository used to set up expectations.
360      */
361     ref Repository!T.Mock expect()
362     {
363         return *this.repository;
364     }
365 
366     /**
367      * Verifies that certain expectation requirements were satisfied.
368      *
369      * Throws: $(D_PSYMBOL ExpectationViolationException) if those issues occur.
370      */
371     void verify()
372     {
373         scope (failure)
374         {
375             static foreach (i, expectation; this.repository.expectationTuple.Methods)
376             {
377                 static foreach (j, Overload; expectation.Overloads)
378                 {
379                     this.expect.expectationTuple.methods[i].overloads[j].clear();
380                 }
381             }
382         }
383 
384         static foreach (i, expectation; this.repository.expectationTuple.Methods)
385         {
386             static foreach (j, Overload; expectation.Overloads)
387             {
388                 foreach (ref call; this.expect.expectationTuple.methods[i].overloads[j])
389                 {
390                     enforce(call.repeat_ <= 0,
391                         expectationViolationException!T(expectation.name, call.arguments));
392                 }
393             }
394         }
395     }
396 
397     /**
398      * Accept expected calls in a random order.
399      *
400      * Returns: $(D_KEYWORD this).
401      */
402     public typeof(this) unordered()
403     {
404         this.repository.expectationTuple.ordered = false;
405         return this;
406     }
407 
408     alias get this;
409 }
410 
411 /**
412  * Stub builder.
413  *
414  * Params:
415  *     T = Mocked type.
416  */
417 final class Stubbed(T) : Builder!T
418 {
419     private Repository!T.Stub* repository;
420 
421     /**
422      * Params:
423      *     mock = Stubbed object.
424      *     repository = Stub repository.
425      */
426     this(T mock, ref Repository!T.Stub repository)
427     in (mock !is null)
428     {
429         super(mock);
430         this.repository = &repository;
431     }
432 
433     /**
434      * Returns: Repository used to set up stubbed methods.
435      */
436     ref Repository!T.Stub stub()
437     {
438         return *this.repository;
439     }
440 
441     alias get this;
442 }
443 
444 /**
445  * $(D_PSYMBOL Call) represents a single call of a mocked method.
446  *
447  * Params:
448  *     F = Function represented by this $(D_PSYMBOL Call).
449  */
450 mixin template Call(alias F)
451 {
452     private alias Function = F;
453 
454     /// Return type of the mocked method.
455     private alias Return = ReturnType!F;
456 
457     // Parameters accepted by the mocked method.
458     private alias ParameterTypes = .Parameters!F;
459 
460     static if (is(FunctionTypeOf!F PT == __parameters))
461     {
462         /// Arguments passed to set the expectation up.
463         private alias Parameters = PT;
464     }
465     else
466     {
467         static assert(false, typeof(T).stringof ~ " is not a function");
468     }
469 
470     /// Attribute set of the mocked method.
471     private alias qualifiers = AliasSeq!(__traits(getFunctionAttributes, F));
472 
473     private enum concatenatedQualifiers = [qualifiers].join(" ");
474 
475     mixin("alias Action = Return delegate(ParameterTypes) "
476             ~ concatenatedQualifiers ~ ";");
477 
478     private bool passThrough_ = false;
479     private Exception exception;
480     private Action action_;
481 
482     /// Expected arguments if any.
483     private alias Arguments = Maybe!ParameterTypes;
484 
485     /// ditto
486     private Arguments arguments;
487 
488     static if (!is(Return == void))
489     {
490         private Return return_ = Return.init;
491 
492         /**
493          * Set the value to return when method matching this expectation is called on a mock object.
494          *
495          * Params:
496          *     value = the value to return
497          *
498          * Returns: $(D_KEYWORD this).
499          */
500         public ref typeof(this) returns(Return value) @trusted
501         {
502             import core.stdc.string : memcpy;
503 
504             // discard possible immutable
505             memcpy(cast(void*) &this.return_, cast(void*) &value, Return.sizeof);
506 
507             return this;
508         }
509     }
510 
511     /**
512      * Instead of returning or throwing a given value, pass the call through to
513      * the mocked type object.
514      *
515      * This is useful for example for enabling use of mock object in hashmaps
516      * by enabling `toHash` and `opEquals` of your class.
517      *
518      * Returns: $(D_KEYWORD this).
519      */
520     public ref typeof(this) passThrough()
521     {
522         this.passThrough_ = true;
523 
524         return this;
525     }
526 
527     /**
528      * Compares arguments of this call with the given arguments.
529      *
530      * Params:
531      *     Options = Functions used for comparison.
532      *     arguments = Arguments.
533      *
534      * Returns: Whether the arguments of this call are equal to the given
535      *          arguments.
536      */
537     public bool compareArguments(Options)(ParameterTypes arguments)
538     {
539         Options options;
540 
541         static foreach (i, argument; arguments)
542         {
543             if (!this.arguments.isNull && !options.equal(this.arguments.get!i, argument))
544             {
545                 return false;
546             }
547         }
548         return true;
549     }
550 
551     /**
552      * When the method which matches this expectation is called, throw the given
553      * exception. If there are any actions specified (via the action method),
554      * they will not be executed.
555      *
556      * Params:
557      *     exception = The exception to throw.
558      *
559      * Returns: $(D_KEYWORD this).
560      */
561     public ref typeof(this) throws(Exception exception)
562     {
563         this.exception = exception;
564 
565         return this;
566     }
567 
568     /**
569      * Creates an exception of type $(D_PARAM E) and throws it when the method
570      * which matches this expectation is called.
571      *
572      * Params:
573      *     E = Exception type to throw.
574      *     msg = The error message to put in the exception if it is thrown.
575      *     file = The source file of the caller.
576      *     line = The line number of the caller.
577      *
578      * Returns: $(D_KEYWORD this).
579      */
580     public ref typeof(this) throws(E : Exception = Exception)(
581             string msg,
582             string file = __FILE__, size_t line = __LINE__)
583     {
584         this.exception = new E(msg, file, line);
585 
586         return this;
587     }
588 
589     /**
590      * When the method which matches this expectation is called execute the
591      * given delegate. The delegate's signature must match the signature
592      * of the called method.
593      *
594      * The called method will return whatever the given delegate returns.
595      *
596      * Params:
597      *     callback = Callable should be called.
598      *
599      * Returns: $(D_KEYWORD this).
600      */
601     public ref typeof(this) action(Action callback)
602     {
603         this.action_ = callback;
604 
605         return this;
606     }
607 }
608 
609 /// ditto
610 struct MockCall(alias F)
611 {
612     mixin Call!F;
613 
614     private uint repeat_ = 1;
615     private size_t index = 0;
616 
617     @disable this();
618 
619     /**
620      * Params:
621      *     index = Call is expected to be at position $(D_PARAM index) among all
622      *             calls on the given mock.
623      */
624     public this(size_t index)
625     {
626         this.index = index;
627     }
628 
629     /**
630      * This expectation will match exactly $(D_PARAM times) times.
631      *
632      * Preconditions:
633      *
634      * $(D_CODE times > 0).
635      *
636      * Params:
637      *     times = The number of calls the expectation will match.
638      *
639      * Returns: $(D_KEYWORD this).
640      */
641     public ref typeof(this) repeat(uint times)
642     in (times > 0)
643     {
644         this.repeat_ = times;
645 
646         return this;
647     }
648 
649     /**
650      * This expectation will match to any number of calls.
651      *
652      * Returns: $(D_KEYWORD this).
653      */
654     public ref typeof(this) repeatAny()
655     {
656         this.repeat_ = 0;
657 
658         return this;
659     }
660 }
661 
662 /// ditto
663 struct StubCall(alias F)
664 {
665     mixin Call!F;
666 }
667 
668 /**
669  * Function overload representation.
670  *
671  * Params:
672  *     C = Single mocked method call.
673  */
674 private struct Overload(C)
675 {
676     /// Single mocked method call.
677     alias Call = C;
678 
679     /// Return type of the mocked method.
680     alias Return = Call.Return;
681 
682     // Parameters accepted by the mocked method.
683     alias ParameterTypes = Call.ParameterTypes;
684 
685     /// Arguments passed to set the expectation up.
686     alias Parameters = Call.Parameters;
687 
688     /// Attribute set of the mocked method.
689     alias qualifiers = Call.qualifiers;
690 
691     /// Expected arguments if any.
692     alias Arguments = Call.Arguments;
693 
694     /// Expected calls.
695     Call[] calls;
696 
697     /**
698      * Returns: Whether any expected calls are in the queue.
699      */
700     @property bool empty()
701     {
702         return this.calls.empty;
703     }
704 
705     /**
706      * Returns: The next expected call.
707      */
708     ref Call front()
709     in (!this.calls.empty)
710     {
711         return this.calls.front;
712     }
713 
714     /**
715      * Returns: The last expected call.
716      */
717     ref Call back()
718     in (!this.calls.empty)
719     {
720         return this.calls.back;
721     }
722 
723     /**
724       * Removes the next expected call from the queue.
725       */
726     void popFront()
727     {
728         this.calls.popFront;
729     }
730 
731     /**
732       * Removes the last expected call from the queue.
733       */
734     void popBack()
735     {
736         this.calls.popBack;
737     }
738 
739     /**
740      * Clears the queue.
741      */
742     void clear()
743     {
744         this.calls = [];
745     }
746 
747     /**
748      * Returns: Number of the queue.
749      */
750     @property size_t length()
751     {
752         return this.calls.length;
753     }
754 
755     /**
756      * Returns: $(D_KEYWORD this).
757      */
758     public Overload save()
759     {
760         return this;
761     }
762 
763     /**
764      * Params:
765      *     i = Index.
766      *
767      * Returns: The element at index $(D_PARAM i).
768      */
769     public ref Call opIndex(size_t i)
770     in (i < this.calls.length)
771     {
772         return this.calls[i];
773     }
774 }
775 
776 /**
777  * $(D_PSYMBOL ExpectationSetup) contains all overloads of a single method.
778  *
779  * Params:
780  *     Call = Call interface (mock or stub).
781  *     T = Mocked type.
782  *     member = Mocked method name.
783  */
784 private struct ExpectationSetup(alias Call, T, string member)
785 {
786     enum string name = member;
787 
788     enum bool isVirtualMethod(alias F) = __traits(isVirtualMethod, F);
789     alias VirtualMethods = Filter!(isVirtualMethod, __traits(getOverloads, T, member));
790     alias Overloads = staticMap!(Overload, staticMap!(Call, VirtualMethods));
791 
792     Overloads overloads;
793 }
794 
795 /**
796  * $(D_PSYMBOL Repository) contains all mocked methods of a single class.
797  *
798  * Params:
799  *     T = Mocked type.
800  */
801 private template Repository(T)
802 if (isPolymorphicType!T)
803 {
804     enum isVirtualMethod(string member) =
805         __traits(isVirtualMethod, __traits(getMember, T, member));
806     alias allMembers = __traits(allMembers, T);
807     alias VirtualMethods = Filter!(isVirtualMethod, allMembers);
808 
809     struct MockSetup
810     {
811         alias Methods = staticMap!(ApplyLeft!(ExpectationSetup, MockCall, T), VirtualMethods);
812         alias Type = T;
813         enum string code = mockCode;
814 
815         Methods methods;
816         private size_t lastCall_;
817         public size_t actualCall;
818         bool ordered = true;
819 
820         public @property size_t lastCall()
821         {
822             return ++this.lastCall_;
823         }
824     }
825 
826     struct StubSetup
827     {
828         alias Methods = staticMap!(ApplyLeft!(ExpectationSetup, StubCall, T), VirtualMethods);
829         alias Type = T;
830         enum string code = stubCode;
831         Methods methods;
832     }
833 
834     struct Mock
835     {
836         MockSetup expectationTuple;
837 
838         static foreach (i, member; VirtualMethods)
839         {
840             static foreach (j, overload; expectationTuple.Methods[i].Overloads)
841             {
842                 mixin(format!mockProperty(member, i, j));
843             }
844 
845             static if (!anySatisfy!(hasNoArguments, expectationTuple.Methods[i].Overloads))
846             {
847                 mixin(format!mockProperty0(member, i));
848             }
849         }
850     }
851 
852     struct Stub
853     {
854         StubSetup expectationTuple;
855 
856         static foreach (i, member; VirtualMethods)
857         {
858             static foreach (j, overload; expectationTuple.Methods[i].Overloads)
859             {
860                 mixin(format!stubProperty(member, i, j));
861             }
862 
863             static if (!anySatisfy!(hasNoArguments, expectationTuple.Methods[i].Overloads))
864             {
865                 mixin(format!stubProperty0(member, i));
866             }
867         }
868     }
869 }
870 
871 private enum string mockProperty0 = q{
872     ref auto %1$s(Args...)()
873     {
874         static if (Args.length == 0)
875         {
876             enum ptrdiff_t index = 0;
877         }
878         else
879         {
880             enum ptrdiff_t index = matchArguments!(Pack!Args, expectationTuple.Methods[%2$s].Overloads);
881         }
882         static assert(index >= 0,
883                 "%1$s overload with the given argument types could not be found");
884 
885         this.expectationTuple.methods[%2$s].overloads[index].calls ~=
886             this.expectationTuple.Methods[%2$s].Overloads[index].Call(this.expectationTuple.lastCall);
887         return this.expectationTuple.methods[%2$s].overloads[index].back;
888     }
889 };
890 
891 private enum string mockProperty = q{
892     ref auto %1$s(overload.Parameters arguments)
893     {
894         this.expectationTuple.methods[%2$s].overloads[%3$s].calls ~=
895             overload.Call(this.expectationTuple.lastCall);
896         this.expectationTuple.methods[%2$s].overloads[%3$s].back.arguments = arguments;
897         return this.expectationTuple.methods[%2$s].overloads[%3$s].back;
898     }
899 };
900 
901 private enum string stubProperty = q{
902     ref auto %1$s(overload.Parameters arguments) return
903     {
904         /**
905          * Why is this a nested function?
906          * Due to the `__parameters` hack used to form `overload.Parameters`,
907          * the individual parameter names of the overload - *not* just `arguments`! -
908          * are also valid in this scope and may introduce identifier collisions
909          * with `call`.
910          * Sidestep this by opening a new function.
911          */
912         return delegate ref() return {
913             foreach (i, call; this.expectationTuple.methods[%2$s].overloads[%3$s].calls)
914             {
915                 if (!call.arguments.isNull && call.arguments.get == tuple(arguments))
916                 {
917                     return this.expectationTuple.methods[%2$s].overloads[%3$s].calls[i];
918                 }
919             }
920             this.expectationTuple.methods[%2$s].overloads[%3$s].calls ~= overload.Call();
921             this.expectationTuple.methods[%2$s].overloads[%3$s].back.arguments = arguments;
922             return this.expectationTuple.methods[%2$s].overloads[%3$s].back;
923         }();
924     }
925 };
926 
927 private enum string stubProperty0 = q{
928     ref auto %1$s(Args...)()
929     {
930         static if (Args.length == 0)
931         {
932             enum ptrdiff_t index = 0;
933         }
934         else
935         {
936             enum ptrdiff_t index = matchArguments!(Pack!Args, expectationTuple.Methods[%2$s].Overloads);
937         }
938         static assert(index >= 0,
939                 "%1$s overload with the given argument types could not be found");
940 
941         if (this.expectationTuple.methods[%2$s].overloads[index].calls.empty)
942         {
943             this.expectationTuple.methods[%2$s].overloads[index].calls ~=
944                 this.expectationTuple.Methods[%2$s].Overloads[index].Call();
945             return this.expectationTuple.methods[%2$s].overloads[index].back;
946         }
947         else
948         {
949             return this.expectationTuple.methods[%2$s].overloads[index].calls.back;
950         }
951     }
952 };
953 
954 private template matchArguments(Needle, Haystack...)
955 {
956     private template matchArgumentsImpl(ptrdiff_t i, Haystack...)
957     {
958         static if (Haystack.length == 0)
959         {
960             enum ptrdiff_t matchArgumentsImpl = -1;
961         }
962         else static if (__traits(isSame, Needle, Pack!(Haystack[0].ParameterTypes)))
963         {
964             enum ptrdiff_t matchArgumentsImpl = i;
965         }
966         else
967         {
968             enum ptrdiff_t matchArgumentsImpl = matchArgumentsImpl!(i + 1, Haystack[1 .. $]);
969         }
970     }
971     enum ptrdiff_t matchArguments = matchArgumentsImpl!(0, Haystack);
972 }
973 
974 private enum bool hasNoArguments(T) = T.Parameters.length == 0;
975 
976 /**
977  * Mock builder used by the mocks and stubs.
978  *
979  * Params:
980  *     T = Mocked type.
981  */
982 abstract class Builder(T)
983 {
984     /// Mocked object instance.
985     protected T mock;
986 
987     invariant(mock !is null);
988 
989     /**
990      * Params:
991      *     mock = Mocked object.
992      */
993     this(T mock)
994     in (mock !is null)
995     {
996         this.mock = mock;
997     }
998 
999     /**
1000      * Returns: Mocked object.
1001      */
1002     T get() @nogc nothrow pure @safe
1003     {
1004         return this.mock;
1005     }
1006 
1007     static if (is(T == class))
1008     {
1009         /**
1010          * Forward default object methods to the mock.
1011          */
1012         override size_t toHash()
1013         {
1014             return get().toHash();
1015         }
1016 
1017         /// ditto
1018         override string toString()
1019         {
1020             return get().toString();
1021         }
1022 
1023         /// ditto
1024         override int opCmp(Object o)
1025         {
1026             return get().opCmp(o);
1027         }
1028 
1029         /// ditto
1030         override bool opEquals(Object o)
1031         {
1032             return get().opEquals(o);
1033         }
1034     }
1035 }
1036 
1037 private mixin template NestedMock(Repository, Options, Args...)
1038 {
1039     final class Mock : Repository.expectationTuple.Type
1040     {
1041         import std.string : join;
1042 
1043         private Repository expectationSetup;
1044 
1045         static if (__traits(hasMember, Repository.expectationTuple.Type, "__ctor")
1046                 && Args.length > 0)
1047         {
1048             this(ref Args args)
1049             {
1050                 super(args);
1051             }
1052         }
1053         else static if (__traits(hasMember, Repository.expectationTuple.Type, "__ctor"))
1054         {
1055             this()
1056             {
1057                 super(Parameters!(Repository.expectationTuple.Type.__ctor).init);
1058             }
1059         }
1060 
1061         static foreach (j, expectation; expectationSetup.expectationTuple.Methods)
1062         {
1063             static foreach (i, Overload; expectation.Overloads)
1064             {
1065                 mixin(["override", Overload.qualifiers, "Overload.Return", expectation.name].join(" ") ~ q{
1066                     (Overload.ParameterTypes arguments)
1067                     {
1068                         mixin(Repository.expectationTuple.code);
1069                     }
1070                 });
1071             }
1072         }
1073     }
1074 }