1 module dmocks.expectation; 2 3 import dmocks.action; 4 import dmocks.arguments; 5 import dmocks.call; 6 import dmocks.model; 7 import dmocks.name_match; 8 import dmocks.qualifiers; 9 import dmocks.util; 10 import std.conv; 11 import std.range; 12 import std.traits; 13 14 package: 15 16 class CallExpectation : Expectation 17 { 18 this(MockId object, NameMatch name, QualifierMatch qualifiers, ArgumentsMatch args, TypeInfo returnType) 19 { 20 this.object = object; 21 this.name = name; 22 this.repeatInterval = Interval(1, 1); 23 this.arguments = args; 24 this.qualifiers = qualifiers; 25 this.action = new Action(returnType); 26 _matchedCalls = []; 27 } 28 29 MockId object; 30 NameMatch name; 31 QualifierMatch qualifiers; 32 ArgumentsMatch arguments; 33 private Call[] _matchedCalls; 34 Interval repeatInterval; 35 36 Action action; 37 38 override string toString(string indentation) 39 { 40 auto apndr = appender!(string); 41 apndr.put(indentation); 42 apndr.put("Expectation: "); 43 bool details = !satisfied; 44 45 if (!details) 46 apndr.put("satisfied, "); 47 else 48 apndr.put("not satisfied, "); 49 50 apndr.put("Method: "); 51 apndr.put(name.toString()); 52 apndr.put(" "); 53 apndr.put(arguments.toString()); 54 apndr.put(" "); 55 apndr.put(qualifiers.toString()); 56 apndr.put("\n"); 57 apndr.put(indentation); 58 apndr.put("ExpectedCalls: "); 59 apndr.put(repeatInterval.toString()); 60 61 if (details) 62 { 63 apndr.put("\n" ~ indentation ~ "Calls: " ~ _matchedCalls.length.to!string); 64 foreach (Call call; _matchedCalls) 65 { 66 apndr.put("\n" ~ indentation ~ " " ~ call.toString()); 67 } 68 } 69 return apndr.data; 70 } 71 72 string diffToString(Call call) 73 in (name.matches(call.name), "logic error: trying to format expectation diff to call that doesn't match it") 74 { 75 string result; 76 77 if (object != call.object) 78 { 79 result ~= yellow("(different object)."); 80 } 81 result ~= call.name; 82 result ~= " "; 83 result ~= arguments.diffToString(call.arguments); 84 result ~= " "; 85 result ~= qualifiers.diffToString(call.qualifiers); 86 if (_matchedCalls.length >= repeatInterval.Max) 87 { 88 result ~= " (called too many times)"; 89 } 90 result ~= "\n"; 91 return result; 92 } 93 94 override string toString() 95 { 96 return toString(""); 97 } 98 99 CallExpectation match(Call call) 100 { 101 debugLog("Expectation.match:"); 102 if (object != call.object) 103 { 104 debugLog("object doesn't match"); 105 return null; 106 } 107 if (!name.matches(call.name)) 108 { 109 debugLog("name doesn't match"); 110 return null; 111 } 112 if (!qualifiers.matches(call.qualifiers)) 113 { 114 debugLog("qualifiers don't match"); 115 return null; 116 } 117 if (!arguments.matches(call.arguments)) 118 { 119 debugLog("arguments don't match"); 120 return null; 121 } 122 if (_matchedCalls.length >= repeatInterval.Max) 123 { 124 debugLog("repeat interval desn't match"); 125 return null; 126 } 127 debugLog("full match"); 128 _matchedCalls ~= call; 129 return this; 130 } 131 132 bool satisfied() 133 { 134 return _matchedCalls.length <= repeatInterval.Max && _matchedCalls.length >= repeatInterval.Min; 135 } 136 } 137 138 class GroupExpectation : Expectation 139 { 140 this() 141 { 142 repeatInterval = Interval(1, 1); 143 expectations = []; 144 } 145 146 Expectation[] expectations; 147 bool ordered; 148 Interval repeatInterval; 149 150 CallExpectation match(Call call) 151 { 152 // match call to expectation 153 foreach (Expectation expectation; expectations) 154 { 155 CallExpectation e = expectation.match(call); 156 if (e !is null) 157 return e; 158 if (ordered && !expectation.satisfied()) 159 return null; 160 } 161 return null; 162 } 163 164 void addExpectation(Expectation expectation) 165 { 166 expectations ~= expectation; 167 } 168 169 bool satisfied() 170 { 171 foreach (Expectation expectation; expectations) 172 { 173 if (!expectation.satisfied()) 174 return false; 175 } 176 return true; 177 } 178 179 override string toString(string indentation) 180 { 181 auto apndr = appender!(string); 182 apndr.put(indentation); 183 apndr.put("GroupExpectation: "); 184 bool details = !satisfied; 185 186 if (!details) 187 apndr.put("satisfied, "); 188 else 189 apndr.put("not satisfied, "); 190 191 if (ordered) 192 apndr.put("ordered, "); 193 else 194 apndr.put("unordered, "); 195 196 apndr.put("Interval: "); 197 apndr.put(repeatInterval.toString()); 198 199 if (details) 200 { 201 foreach (Expectation expectation; expectations) 202 { 203 apndr.put("\n"); 204 apndr.put(expectation.toString(indentation ~ " ")); 205 } 206 } 207 return apndr.data; 208 } 209 210 override string toString() 211 { 212 return toString(""); 213 } 214 } 215 216 GroupExpectation createGroupExpectation(bool ordered) 217 { 218 auto ret = new GroupExpectation(); 219 ret.ordered = ordered; 220 return ret; 221 } 222 223 // composite design pattern 224 interface Expectation 225 { 226 CallExpectation match(Call call); 227 bool satisfied(); 228 string toString(string indentation); 229 string toString(); 230 } 231 232 CallExpectation createExpectation(alias METHOD, ARGS...)(MockId object, string name, ARGS args) 233 { 234 auto ret = new CallExpectation(object, new NameMatchText(name), qualifierMatch!METHOD, 235 new StrictArgumentsMatch(arguments(args)), typeid(ReturnType!(FunctionTypeOf!(METHOD)))); 236 return ret; 237 }