1 module dmocks.repository;
2 
3 import dmocks.action;
4 import dmocks.call;
5 import dmocks.expectation;
6 import dmocks.model;
7 import dmocks.util;
8 import std.traits;
9 
10 package:
11 
12 class MockRepository
13 {
14     // TODO: split this up somehow!
15     private bool _allowDefaults = false;
16     private bool _recording = true;
17     private bool _ordered = false;
18     private bool _allowUnexpected = false;
19 
20     private Call[] _calls = [];
21     private Call[] _unexpectedCalls = [];
22     private GroupExpectation _rootGroupExpectation;
23     private CallExpectation _lastRecordedCallExpectation; // stores last call added to _lastGroupExpectation
24     private Call _lastRecordedCall; // stores last call with which _lastRecordedCallExpectation was created
25     private GroupExpectation _lastGroupExpectation; // stores last group added to _rootGroupExpectation
26 
27     private void CheckLastCallSetup ()
28     {
29         if (_allowDefaults || _lastRecordedCallExpectation is null || _lastRecordedCallExpectation.action.hasAction)
30         {
31             return;
32         }
33 
34         throw new MocksSetupException(
35                 "Last expectation: if you do not specify the AllowDefaults option, you need to return a value, throw an exception, execute a delegate, or pass through to base function. The expectation is: " ~ _lastRecordedCallExpectation.toString());
36     }
37 
38     this()
39     {   
40         _rootGroupExpectation = createGroupExpectation(false);
41         Ordered(false);
42     }
43 
44 
45     void AllowDefaults (bool value)
46     {
47         _allowDefaults = value;
48     }
49 
50     void AllowUnexpected(bool value)
51     {
52         _allowUnexpected = value;
53     }
54 
55     bool AllowUnexpected()
56     {
57         return _allowUnexpected;
58     }
59 
60     bool Recording ()
61     {
62         return _recording;
63     }
64 
65     bool Ordered ()
66     {
67         return _ordered;
68     }
69 
70     void Replay ()
71     {
72         CheckLastCallSetup();
73         _recording = false;
74     }
75 
76     void BackToRecord ()
77     {
78         _recording = true;
79     }
80 
81     void Ordered(bool value)
82     {
83         debugLog("SETTING ORDERED: %s", value);
84         _ordered = value;
85         _lastGroupExpectation = createGroupExpectation(_ordered);
86         _rootGroupExpectation.addExpectation(_lastGroupExpectation);
87     }
88 
89     void Record(CallExpectation expectation, Call call)
90     {
91         CheckLastCallSetup();
92         _lastGroupExpectation.addExpectation(expectation);
93         _lastRecordedCallExpectation = expectation;
94         _lastRecordedCall = call;
95     }
96 
97     @trusted public auto MethodCall (alias METHOD, ARGS...) (MockId mocked, string name, ARGS args)
98     {
99         alias ReturnType!(FunctionTypeOf!(METHOD)) TReturn;
100 
101         ReturnOrPass!(TReturn) rope;
102         auto call = createCall!METHOD(mocked, name, args);
103         debugLog("checking Recording...");
104         if (Recording)
105         {
106             auto expectation = createExpectation!(METHOD)(mocked, name, args);
107             Record(expectation, call);
108             return rope;
109         }
110 
111         debugLog("checking for matching expectation...");
112         auto expectation = Match(call);
113 
114         debugLog("checking if expectation is null...");
115         if (expectation is null)
116         {
117             if (AllowUnexpected())
118                 return rope;
119             throw new ExpectationViolationException("Unexpected call to method: " ~ call.toString());
120         }
121 
122         rope = expectation.action.getActor().act!(TReturn, ARGS)(args);
123         debugLog("returning...");
124         return rope;
125     }
126 
127     CallExpectation Match(Call call)
128     {
129         _calls ~= call;
130         auto exp = _rootGroupExpectation.match(call);
131         if (exp is null)
132             _unexpectedCalls ~= _calls;
133         return exp;
134     }
135 
136     CallExpectation LastRecordedCallExpectation ()
137     {
138         return _lastRecordedCallExpectation;
139     }
140 
141     Call LastRecordedCall ()
142     {
143         return _lastRecordedCall;
144     }
145 
146     void Verify (bool checkUnmatchedExpectations, bool checkUnexpectedCalls)
147     {
148         string expectationError = "";
149         if (checkUnmatchedExpectations && !_rootGroupExpectation.satisfied)
150             expectationError~="\n" ~ _rootGroupExpectation.toString();
151                 
152         if (checkUnexpectedCalls && _unexpectedCalls.length > 0)
153             expectationError~="\n" ~ UnexpectedCallsReport;
154         if (expectationError != "")
155             throw new ExpectationViolationException(expectationError);
156     }
157 
158     string UnexpectedCallsReport()
159     {
160         import std.array;
161         auto apndr = appender!(string);
162         apndr.put("Unexpected calls(calls):\n");
163         foreach(Call ev; _unexpectedCalls)
164         {
165             apndr.put(ev.toString());
166             apndr.put("\n");
167         }
168         return apndr.data;
169     }
170 
171     @("repository record/replay")
172     unittest
173     {
174         MockRepository r = new MockRepository();
175         assert (r.Recording());
176         r.Replay();
177         assert (!r.Recording());
178         r.BackToRecord();
179         assert (r.Recording());
180     }
181 
182     
183     @("test for correctly formulated template")
184     unittest
185     {
186         class A
187         {
188             public void a()
189             {
190             }
191         }
192         auto a = new A;
193         auto c = new MockRepository();
194         auto mid = new MockId;
195         //c.Call!(a.a)(mid, "a");
196         static assert(__traits(compiles, c.MethodCall!(a.a)(mid, "a")));
197     }
198 }