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 }