1 module mocked.mocker;
2 
3 import mocked.builder;
4 import mocked.error;
5 import mocked.meta;
6 import mocked.option;
7 import std.conv;
8 import std.format : format;
9 import std.traits;
10 
11 private enum string overloadingCode = q{
12     %2$s Overload.Return %1$s(Overload.ParameterTypes arguments)
13     {
14         auto overloads = builder.expectationTuple[j].overloads[i];
15 
16         if (overloads.empty)
17         {
18             throw unexpectedCallError!(typeof(super), Overload.ParameterTypes)("%1$s", arguments);
19         }
20         if (!overloads.front.ignoreArgs_
21                 && !overloads.front.compareArguments!options(arguments))
22         {
23             builder.expectationTuple[j].overloads[i].clear();
24 
25             throw unexpectedArgumentError!(typeof(super),
26                     Overload.ParameterTypes, Overload.Arguments)(
27                     "%1$s", arguments, overloads.front.arguments);
28         }
29 
30         static if (is(Overload.Return == void))
31         {
32             if (overloads.front.action_ !is null)
33             {
34                 overloads.front.action_(arguments);
35             }
36         }
37         else
38         {
39             Overload.Return ret = void;
40 
41             if (overloads.front.action_ !is null)
42             {
43                 ret = overloads.front.action_(arguments);
44             }
45             else
46             {
47                 ret = overloads.front.return_;
48             }
49         }
50 
51         static if (!is(T == interface) && is(Overload.Return == void))
52         {
53             if (overloads.front.passThrough_)
54             {
55                 __traits(getMember, super, "%1$s")(arguments);
56             }
57         }
58         else static if (!is(T == interface))
59         {
60             if (overloads.front.passThrough_)
61             {
62                 ret = __traits(getMember, super, "%1$s")(arguments);
63             }
64         }
65 
66         if (builder.expectationTuple[j].overloads[i].front.repeat_ > 1)
67         {
68             --builder.expectationTuple[j].overloads[i].front.repeat_;
69         }
70         else if (builder.expectationTuple[j].overloads[i].front.repeat_ == 1)
71         {
72             builder.expectationTuple[j].overloads[i].popFront;
73         }
74 
75         static if (!canFind!("nothrow", Overload.qualifiers))
76         {
77             if (overloads.front.exception !is null)
78             {
79                 throw overloads.front.exception;
80             }
81         }
82         static if (!is(Overload.Return == void))
83         {
84             return ret;
85         }
86     }
87 };
88 
89 /**
90  * Mocker instance with default options.
91  *
92  * See_Also: $(D_PSYMBOL Factory), $(D_PSYMBOL configure).
93  */
94 alias Mocker = Factory!(Options!());
95 
96 /**
97  * Constructs a mocker with options passed as $(D_PARAM Args).
98  *
99  * Params:
100  *     Args = Mocker options.
101  *
102  * See_Also: $(D_PSYMBOL Factory), $(D_PSYMBOL Mocker).
103  */
104 auto configure(Args...)()
105 {
106     return Factory!(Options!Args)();
107 }
108 
109 /**
110  * A class through which one creates mock objects and manages expectations
111  * about calls to their methods.
112  *
113  * Params:
114  *     Options = Mocker $(D_PSYMBOL Options).
115  *
116  * See_Also: $(D_PSYMBOL Mocker), $(D_PSYMBOL CustomMocker).
117  */
118 struct Factory(Options)
119 {
120     Verifiable[] repositories;
121     private enum Options options = Options();
122 
123     /**
124      * Mocks the type $(D_PARAM T).
125      *
126      * Params:
127      *     T = The type to mock.
128      *     Args = Constructor parameter types.
129      *     args = Constructor arguments.
130      *
131      * Returns: A mock builder.
132      */
133     auto mock(T, Args...)(Args args)
134     {
135         Repository!T builder;
136 
137         class Mock : T
138         {
139             static if (__traits(hasMember, T, "__ctor") && Args.length > 0)
140             {
141                 this()
142                 {
143                     super(args);
144                 }
145             }
146             else static if (__traits(hasMember, T, "__ctor"))
147             {
148                 this()
149                 {
150                     super(Parameters!(T.__ctor).init);
151                 }
152             }
153 
154             static foreach (j, expectation; builder.ExpectationTuple)
155             {
156                 static foreach (i, Overload; expectation.Overloads)
157                 {
158                     static if (is(T == class))
159                     {
160                         mixin(format!overloadingCode(expectation.name,
161                                 unwords!("override", Overload.qualifiers)));
162                     }
163                     else
164                     {
165                         mixin(format!overloadingCode(expectation.name,
166                                 unwords!(Overload.qualifiers)));
167                     }
168                 }
169             }
170         }
171 
172         auto mock = new Mock();
173         auto repository = new Mocked!T(mock, builder);
174 
175         this.repositories ~= repository;
176         return repository;
177     }
178 
179     /**
180      * Verifies that certain expectation requirements were satisfied.
181      *
182      * Throws: $(D_PSYMBOL ExpectationViolationException) if those issues occur.
183      */
184     void verify()
185     {
186         foreach (repository; this.repositories)
187         {
188             repository.verify;
189         }
190     }
191 
192     ~this()
193     {
194         verify;
195     }
196 }