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 /** 264 * A class through which one creates mock objects and manages expectations 265 * about calls to their methods. 266 * 267 * Params: 268 * Options = Mocker $(D_PSYMBOL Options). 269 * 270 * See_Also: $(D_PSYMBOL Mocker), $(D_PSYMBOL CustomMocker). 271 */ 272 struct Factory(Options) 273 { 274 private DList!Verifiable repositories; 275 276 /** 277 * Mocks the type $(D_PARAM T). 278 * 279 * Params: 280 * T = The type to mock. 281 * Args = Constructor parameter types. 282 * args = Constructor arguments. 283 * 284 * Returns: A mock builder. 285 */ 286 auto mock(T, Args...)(Args args) 287 { 288 mixin NestedMock!(Repository!T.Mock, Options, Args); 289 290 auto mock = new Mock(args); 291 auto mocked = new Mocked!T(mock, mock.expectationSetup); 292 293 this.repositories.insertBack(mocked); 294 return mocked; 295 } 296 297 /** 298 * Stubs the type $(D_PARAM T). 299 * 300 * Params: 301 * T = The type to stub. 302 * Args = Constructor parameter types. 303 * args = Constructor arguments. 304 * 305 * Returns: A stub builder. 306 */ 307 auto stub(T, Args...)(Args args) 308 if (isPolymorphicType!T) 309 { 310 mixin NestedMock!(Repository!T.Stub, Options, Args); 311 312 auto stub = new Mock(args); 313 314 return new Stubbed!T(stub, stub.expectationSetup); 315 } 316 317 /** 318 * Verifies that certain expectation requirements were satisfied. 319 * 320 * Throws: $(D_PSYMBOL ExpectationViolationException) if those issues occur. 321 */ 322 void verify() 323 { 324 this.repositories.each!(repository => repository.verify); 325 } 326 327 ~this() 328 { 329 verify; 330 } 331 } 332 333 /** 334 * Mock builder. 335 * 336 * Params: 337 * T = Mocked type. 338 */ 339 final class Mocked(T) : Builder!T, Verifiable 340 { 341 private Repository!T.Mock* repository; 342 343 /** 344 * Params: 345 * mock = Mocked object. 346 * repository = Mock repository. 347 */ 348 this(T mock, ref Repository!T.Mock repository) 349 in (mock !is null) 350 { 351 super(mock); 352 this.repository = &repository; 353 } 354 355 /** 356 * Returns: Repository used to set up expectations. 357 */ 358 ref Repository!T.Mock expect() 359 { 360 return *this.repository; 361 } 362 363 /** 364 * Verifies that certain expectation requirements were satisfied. 365 * 366 * Throws: $(D_PSYMBOL ExpectationViolationException) if those issues occur. 367 */ 368 void verify() 369 { 370 scope (failure) 371 { 372 static foreach (i, expectation; this.repository.expectationTuple.Methods) 373 { 374 static foreach (j, Overload; expectation.Overloads) 375 { 376 this.expect.expectationTuple.methods[i].overloads[j].clear(); 377 } 378 } 379 } 380 381 static foreach (i, expectation; this.repository.expectationTuple.Methods) 382 { 383 static foreach (j, Overload; expectation.Overloads) 384 { 385 foreach (ref call; this.expect.expectationTuple.methods[i].overloads[j]) 386 { 387 enforce(call.repeat_ <= 0, 388 expectationViolationException!T(expectation.name, call.arguments)); 389 } 390 } 391 } 392 } 393 394 /** 395 * Accept expected calls in a random order. 396 * 397 * Returns: $(D_KEYWORD this). 398 */ 399 public typeof(this) unordered() 400 { 401 this.repository.expectationTuple.ordered = false; 402 return this; 403 } 404 405 alias get this; 406 } 407 408 /** 409 * Stub builder. 410 * 411 * Params: 412 * T = Mocked type. 413 */ 414 final class Stubbed(T) : Builder!T 415 { 416 private Repository!T.Stub* repository; 417 418 /** 419 * Params: 420 * mock = Stubbed object. 421 * repository = Stub repository. 422 */ 423 this(T mock, ref Repository!T.Stub repository) 424 in (mock !is null) 425 { 426 super(mock); 427 this.repository = &repository; 428 } 429 430 /** 431 * Returns: Repository used to set up stubbed methods. 432 */ 433 ref Repository!T.Stub stub() 434 { 435 return *this.repository; 436 } 437 438 alias get this; 439 } 440 441 /** 442 * $(D_PSYMBOL Call) represents a single call of a mocked method. 443 * 444 * Params: 445 * F = Function represented by this $(D_PSYMBOL Call). 446 */ 447 mixin template Call(alias F) 448 { 449 private alias Function = F; 450 451 /// Return type of the mocked method. 452 private alias Return = ReturnType!F; 453 454 // Parameters accepted by the mocked method. 455 private alias ParameterTypes = .Parameters!F; 456 457 static if (is(FunctionTypeOf!F PT == __parameters)) 458 { 459 /// Arguments passed to set the expectation up. 460 private alias Parameters = PT; 461 } 462 else 463 { 464 static assert(false, typeof(T).stringof ~ " is not a function"); 465 } 466 467 /// Attribute set of the mocked method. 468 private alias qualifiers = AliasSeq!(__traits(getFunctionAttributes, F)); 469 470 private enum concatenatedQualifiers = [qualifiers].join(" "); 471 472 mixin("alias Action = Return delegate(ParameterTypes) " 473 ~ concatenatedQualifiers ~ ";"); 474 475 private bool passThrough_ = false; 476 private Exception exception; 477 private Action action_; 478 479 /// Expected arguments if any. 480 private alias Arguments = Maybe!ParameterTypes; 481 482 /// ditto 483 private Arguments arguments; 484 485 static if (!is(Return == void)) 486 { 487 private Return return_ = Return.init; 488 489 /** 490 * Set the value to return when method matching this expectation is called on a mock object. 491 * 492 * Params: 493 * value = the value to return 494 * 495 * Returns: $(D_KEYWORD this). 496 */ 497 public ref typeof(this) returns(Return value) @trusted 498 { 499 import core.stdc.string : memcpy; 500 501 // discard possible immutable 502 memcpy(cast(void*) &this.return_, cast(void*) &value, Return.sizeof); 503 504 return this; 505 } 506 } 507 508 /** 509 * Instead of returning or throwing a given value, pass the call through to 510 * the mocked type object. 511 * 512 * This is useful for example for enabling use of mock object in hashmaps 513 * by enabling `toHash` and `opEquals` of your class. 514 * 515 * Returns: $(D_KEYWORD this). 516 */ 517 public ref typeof(this) passThrough() 518 { 519 this.passThrough_ = true; 520 521 return this; 522 } 523 524 /** 525 * Compares arguments of this call with the given arguments. 526 * 527 * Params: 528 * Options = Functions used for comparison. 529 * arguments = Arguments. 530 * 531 * Returns: Whether the arguments of this call are equal to the given 532 * arguments. 533 */ 534 public bool compareArguments(Options)(ParameterTypes arguments) 535 { 536 Options options; 537 538 static foreach (i, argument; arguments) 539 { 540 if (!this.arguments.isNull && !options.equal(this.arguments.get!i, argument)) 541 { 542 return false; 543 } 544 } 545 return true; 546 } 547 548 /** 549 * When the method which matches this expectation is called, throw the given 550 * exception. If there are any actions specified (via the action method), 551 * they will not be executed. 552 * 553 * Params: 554 * exception = The exception to throw. 555 * 556 * Returns: $(D_KEYWORD this). 557 */ 558 public ref typeof(this) throws(Exception exception) 559 { 560 this.exception = exception; 561 562 return this; 563 } 564 565 /** 566 * Creates an exception of type $(D_PARAM E) and throws it when the method 567 * which matches this expectation is called. 568 * 569 * Params: 570 * E = Exception type to throw. 571 * msg = The error message to put in the exception if it is thrown. 572 * file = The source file of the caller. 573 * line = The line number of the caller. 574 * 575 * Returns: $(D_KEYWORD this). 576 */ 577 public ref typeof(this) throws(E : Exception = Exception)( 578 string msg, 579 string file = __FILE__, size_t line = __LINE__) 580 { 581 this.exception = new E(msg, file, line); 582 583 return this; 584 } 585 586 /** 587 * When the method which matches this expectation is called execute the 588 * given delegate. The delegate's signature must match the signature 589 * of the called method. 590 * 591 * The called method will return whatever the given delegate returns. 592 * 593 * Params: 594 * callback = Callable should be called. 595 * 596 * Returns: $(D_KEYWORD this). 597 */ 598 public ref typeof(this) action(Action callback) 599 { 600 this.action_ = callback; 601 602 return this; 603 } 604 } 605 606 /// ditto 607 struct MockCall(alias F) 608 { 609 mixin Call!F; 610 611 private uint repeat_ = 1; 612 private size_t index = 0; 613 614 @disable this(); 615 616 /** 617 * Params: 618 * index = Call is expected to be at position $(D_PARAM index) among all 619 * calls on the given mock. 620 */ 621 public this(size_t index) 622 { 623 this.index = index; 624 } 625 626 /** 627 * This expectation will match exactly $(D_PARAM times) times. 628 * 629 * Preconditions: 630 * 631 * $(D_CODE times > 0). 632 * 633 * Params: 634 * times = The number of calls the expectation will match. 635 * 636 * Returns: $(D_KEYWORD this). 637 */ 638 public ref typeof(this) repeat(uint times) 639 in (times > 0) 640 { 641 this.repeat_ = times; 642 643 return this; 644 } 645 646 /** 647 * This expectation will match to any number of calls. 648 * 649 * Returns: $(D_KEYWORD this). 650 */ 651 public ref typeof(this) repeatAny() 652 { 653 this.repeat_ = 0; 654 655 return this; 656 } 657 } 658 659 /// ditto 660 struct StubCall(alias F) 661 { 662 mixin Call!F; 663 } 664 665 /** 666 * Function overload representation. 667 * 668 * Params: 669 * C = Single mocked method call. 670 */ 671 private struct Overload(C) 672 { 673 /// Single mocked method call. 674 alias Call = C; 675 676 /// Return type of the mocked method. 677 alias Return = Call.Return; 678 679 // Parameters accepted by the mocked method. 680 alias ParameterTypes = Call.ParameterTypes; 681 682 /// Arguments passed to set the expectation up. 683 alias Parameters = Call.Parameters; 684 685 /// Attribute set of the mocked method. 686 alias qualifiers = Call.qualifiers; 687 688 /// Expected arguments if any. 689 alias Arguments = Call.Arguments; 690 691 /// Expected calls. 692 Call[] calls; 693 694 /** 695 * Returns: Whether any expected calls are in the queue. 696 */ 697 @property bool empty() 698 { 699 return this.calls.empty; 700 } 701 702 /** 703 * Returns: The next expected call. 704 */ 705 ref Call front() 706 in (!this.calls.empty) 707 { 708 return this.calls.front; 709 } 710 711 /** 712 * Returns: The last expected call. 713 */ 714 ref Call back() 715 in (!this.calls.empty) 716 { 717 return this.calls.back; 718 } 719 720 /** 721 * Removes the next expected call from the queue. 722 */ 723 void popFront() 724 { 725 this.calls.popFront; 726 } 727 728 /** 729 * Removes the last expected call from the queue. 730 */ 731 void popBack() 732 { 733 this.calls.popBack; 734 } 735 736 /** 737 * Clears the queue. 738 */ 739 void clear() 740 { 741 this.calls = []; 742 } 743 744 /** 745 * Returns: Number of the queue. 746 */ 747 @property size_t length() 748 { 749 return this.calls.length; 750 } 751 752 /** 753 * Returns: $(D_KEYWORD this). 754 */ 755 public Overload save() 756 { 757 return this; 758 } 759 760 /** 761 * Params: 762 * i = Index. 763 * 764 * Returns: The element at index $(D_PARAM i). 765 */ 766 public ref Call opIndex(size_t i) 767 in (i < this.calls.length) 768 { 769 return this.calls[i]; 770 } 771 } 772 773 /** 774 * $(D_PSYMBOL ExpectationSetup) contains all overloads of a single method. 775 * 776 * Params: 777 * Call = Call interface (mock or stub). 778 * T = Mocked type. 779 * member = Mocked method name. 780 */ 781 private struct ExpectationSetup(alias Call, T, string member) 782 { 783 enum string name = member; 784 785 enum bool isVirtualMethod(alias F) = __traits(isVirtualMethod, F); 786 alias VirtualMethods = Filter!(isVirtualMethod, __traits(getOverloads, T, member)); 787 alias Overloads = staticMap!(Overload, staticMap!(Call, VirtualMethods)); 788 789 Overloads overloads; 790 } 791 792 /** 793 * $(D_PSYMBOL Repository) contains all mocked methods of a single class. 794 * 795 * Params: 796 * T = Mocked type. 797 */ 798 private template Repository(T) 799 if (isPolymorphicType!T) 800 { 801 enum isVirtualMethod(string member) = 802 __traits(isVirtualMethod, __traits(getMember, T, member)); 803 alias allMembers = __traits(allMembers, T); 804 alias VirtualMethods = Filter!(isVirtualMethod, allMembers); 805 806 struct MockSetup 807 { 808 alias Methods = staticMap!(ApplyLeft!(ExpectationSetup, MockCall, T), VirtualMethods); 809 alias Type = T; 810 enum string code = mockCode; 811 812 Methods methods; 813 private size_t lastCall_; 814 public size_t actualCall; 815 bool ordered = true; 816 817 public @property size_t lastCall() 818 { 819 return ++this.lastCall_; 820 } 821 } 822 823 struct StubSetup 824 { 825 alias Methods = staticMap!(ApplyLeft!(ExpectationSetup, StubCall, T), VirtualMethods); 826 alias Type = T; 827 enum string code = stubCode; 828 Methods methods; 829 } 830 831 struct Mock 832 { 833 MockSetup expectationTuple; 834 835 static foreach (i, member; VirtualMethods) 836 { 837 static foreach (j, overload; expectationTuple.Methods[i].Overloads) 838 { 839 mixin(format!mockProperty(member, i, j)); 840 } 841 842 static if (!anySatisfy!(hasNoArguments, expectationTuple.Methods[i].Overloads)) 843 { 844 mixin(format!mockProperty0(member, i)); 845 } 846 } 847 } 848 849 struct Stub 850 { 851 StubSetup expectationTuple; 852 853 static foreach (i, member; VirtualMethods) 854 { 855 static foreach (j, overload; expectationTuple.Methods[i].Overloads) 856 { 857 mixin(format!stubProperty(member, i, j)); 858 } 859 860 static if (!anySatisfy!(hasNoArguments, expectationTuple.Methods[i].Overloads)) 861 { 862 mixin(format!stubProperty0(member, i)); 863 } 864 } 865 } 866 } 867 868 private enum string mockProperty0 = q{ 869 ref auto %1$s(Args...)() 870 { 871 static if (Args.length == 0) 872 { 873 enum ptrdiff_t index = 0; 874 } 875 else 876 { 877 enum ptrdiff_t index = matchArguments!(Pack!Args, expectationTuple.Methods[%2$s].Overloads); 878 } 879 static assert(index >= 0, 880 "%1$s overload with the given argument types could not be found"); 881 882 this.expectationTuple.methods[%2$s].overloads[index].calls ~= 883 this.expectationTuple.Methods[%2$s].Overloads[index].Call(this.expectationTuple.lastCall); 884 return this.expectationTuple.methods[%2$s].overloads[index].back; 885 } 886 }; 887 888 private enum string mockProperty = q{ 889 ref auto %1$s(overload.Parameters arguments) 890 { 891 this.expectationTuple.methods[%2$s].overloads[%3$s].calls ~= 892 overload.Call(this.expectationTuple.lastCall); 893 this.expectationTuple.methods[%2$s].overloads[%3$s].back.arguments = arguments; 894 return this.expectationTuple.methods[%2$s].overloads[%3$s].back; 895 } 896 }; 897 898 private enum string stubProperty = q{ 899 ref auto %1$s(overload.Parameters arguments) 900 { 901 /** 902 * Why is this a nested function? 903 * Due to the `__parameters` hack used to form `overload.Parameters`, 904 * the individual parameter names of the overload - *not* just `arguments`! - 905 * are also valid in this scope and may introduce identifier collisions 906 * with `call`. 907 * Sidestep this by opening a new function. 908 */ 909 return delegate ref(){ 910 foreach (ref call; this.expectationTuple.methods[%2$s].overloads[%3$s]) 911 { 912 if (!call.arguments.isNull && call.arguments.get == tuple(arguments)) 913 { 914 return call; 915 } 916 } 917 this.expectationTuple.methods[%2$s].overloads[%3$s].calls ~= overload.Call(); 918 this.expectationTuple.methods[%2$s].overloads[%3$s].back.arguments = arguments; 919 return this.expectationTuple.methods[%2$s].overloads[%3$s].back; 920 }(); 921 } 922 }; 923 924 private enum string stubProperty0 = q{ 925 ref auto %1$s(Args...)() 926 { 927 static if (Args.length == 0) 928 { 929 enum ptrdiff_t index = 0; 930 } 931 else 932 { 933 enum ptrdiff_t index = matchArguments!(Pack!Args, expectationTuple.Methods[%2$s].Overloads); 934 } 935 static assert(index >= 0, 936 "%1$s overload with the given argument types could not be found"); 937 938 if (this.expectationTuple.methods[%2$s].overloads[index].calls.empty) 939 { 940 this.expectationTuple.methods[%2$s].overloads[index].calls ~= 941 this.expectationTuple.Methods[%2$s].Overloads[index].Call(); 942 return this.expectationTuple.methods[%2$s].overloads[index].back; 943 } 944 else 945 { 946 return this.expectationTuple.methods[%2$s].overloads[index].calls.back; 947 } 948 } 949 }; 950 951 private template matchArguments(Needle, Haystack...) 952 { 953 private template matchArgumentsImpl(ptrdiff_t i, Haystack...) 954 { 955 static if (Haystack.length == 0) 956 { 957 enum ptrdiff_t matchArgumentsImpl = -1; 958 } 959 else static if (__traits(isSame, Needle, Pack!(Haystack[0].ParameterTypes))) 960 { 961 enum ptrdiff_t matchArgumentsImpl = i; 962 } 963 else 964 { 965 enum ptrdiff_t matchArgumentsImpl = matchArgumentsImpl!(i + 1, Haystack[1 .. $]); 966 } 967 } 968 enum ptrdiff_t matchArguments = matchArgumentsImpl!(0, Haystack); 969 } 970 971 private enum bool hasNoArguments(T) = T.Parameters.length == 0; 972 973 /** 974 * Mock builder used by the mocks and stubs. 975 * 976 * Params: 977 * T = Mocked type. 978 */ 979 abstract class Builder(T) 980 { 981 /// Mocked object instance. 982 protected T mock; 983 984 invariant(mock !is null); 985 986 /** 987 * Params: 988 * mock = Mocked object. 989 */ 990 this(T mock) 991 in (mock !is null) 992 { 993 this.mock = mock; 994 } 995 996 /** 997 * Returns: Mocked object. 998 */ 999 T get() @nogc nothrow pure @safe 1000 { 1001 return this.mock; 1002 } 1003 1004 static if (is(T == class)) 1005 { 1006 /** 1007 * Forward default object methods to the mock. 1008 */ 1009 override size_t toHash() 1010 { 1011 return get().toHash(); 1012 } 1013 1014 /// ditto 1015 override string toString() 1016 { 1017 return get().toString(); 1018 } 1019 1020 /// ditto 1021 override int opCmp(Object o) 1022 { 1023 return get().opCmp(o); 1024 } 1025 1026 /// ditto 1027 override bool opEquals(Object o) 1028 { 1029 return get().opEquals(o); 1030 } 1031 } 1032 } 1033 1034 private mixin template NestedMock(Repository, Options, Args...) 1035 { 1036 final class Mock : Repository.expectationTuple.Type 1037 { 1038 import std.string : join; 1039 1040 private Repository expectationSetup; 1041 1042 static if (__traits(hasMember, Repository.expectationTuple.Type, "__ctor") 1043 && Args.length > 0) 1044 { 1045 this(ref Args args) 1046 { 1047 super(args); 1048 } 1049 } 1050 else static if (__traits(hasMember, Repository.expectationTuple.Type, "__ctor")) 1051 { 1052 this() 1053 { 1054 super(Parameters!(Repository.expectationTuple.Type.__ctor).init); 1055 } 1056 } 1057 1058 static foreach (j, expectation; expectationSetup.expectationTuple.Methods) 1059 { 1060 static foreach (i, Overload; expectation.Overloads) 1061 { 1062 mixin(["override", Overload.qualifiers, "Overload.Return", expectation.name].join(" ") ~ q{ 1063 (Overload.ParameterTypes arguments) 1064 { 1065 mixin(Repository.expectationTuple.code); 1066 } 1067 }); 1068 } 1069 } 1070 } 1071 }