1 /** 2 Copyright: 2017 © LLC CERERIS 3 License: MIT 4 Authors: LLC CERERIS 5 */ 6 7 module checkit.mock; 8 9 import checkit.exception; 10 import core.exception; 11 import std..string; 12 13 /// Class for create Mock object 14 class Mock(T) 15 { 16 public: 17 /// Default constructor 18 this() 19 { 20 _implementation = new MockAbstract; 21 } 22 23 /** Add a value to be returned by the mock. 24 25 Params: 26 functionName = The name of function mock. 27 i = The count of realization for override function. 28 values = Are return values. 29 30 Throws: 31 If function not virtual, will throw an AssertError. 32 33 Examples: 34 --- 35 interface Dummy 36 { 37 int test(); 38 } 39 auto mock = new Mock!Dummy; 40 mock.returnValue!"test"(1,2); 41 assert(mock.test() == 1); 42 assert(mock.test() == 2); 43 --- 44 */ 45 void returnValue(string functionName, ubyte i = 0, V...)(V values) 46 { 47 import std.conv: text; 48 49 alias member = Identity!(__traits(getMember, T, functionName)); 50 static assert(__traits(isVirtualMethod, member), "Can't use return value for <" ~ functionName ~ ">"); 51 52 enum varName = functionName ~ text(`_`, i, `_returnValues`); 53 foreach(v; values) 54 { 55 mixin(varName ~ ` ~= v;`); 56 } 57 } 58 59 /// Mock implementation 60 MockAbstract _implementation; 61 62 private: 63 /// Abstract Mock template 64 class MockAbstract: T 65 { 66 import std.conv: to; 67 import std.traits: Parameters, ReturnType; 68 import std.typecons: tuple; 69 mixin(generateDefaultImplementationMixin!T); 70 mixin MockCommon; 71 } 72 73 alias _implementation this; 74 } 75 76 /// Mixin for test behavior 77 mixin template MockCommon() 78 { 79 public: 80 /** Used to assert function call once 81 82 Params: 83 functionName = The name of expected function. 84 message = Exception message. 85 file = The file name that the assert failed in. Should be left as default. 86 line = The file line that the assert failed in. Should be left as default. 87 values = Are expected parameter values. 88 89 Throws: 90 If function is not call with parameters, will throw an UnitTestException. 91 92 Examples: 93 --- 94 interface Dummy 95 { 96 void test(); 97 void test(int a); 98 } 99 100 auto mock = new Mock!Dummy; 101 mock.test(); 102 mock.test(12); 103 mock.expectCalled!"test"(); 104 mock.expectCalled!"test"(12); 105 106 // Will throw an exception like "UnitTestException@example.d(6): <test> expected call with <50> but called with <Tuple!int(12)> 107 mock.exceptCalled!"test"(50); 108 --- 109 */ 110 void expectCalled(string functionName, 111 string message = null, 112 string file = __FILE__, 113 size_t line = __LINE__, 114 V...)(auto ref V values) 115 { 116 expectCalled!(functionName, 1, message, file, line)(values); 117 } 118 119 /** Used to assert function not call 120 121 Params: 122 functionName = The name of expected function. 123 message = Exception message. 124 file = The file name that the assert failed in. Should be left as default. 125 line = The file line that the assert failed in. Should be left as default. 126 values = Are expected parameter values. 127 128 Throws: 129 If function is call with parameters, will throw an UnitTestException. 130 131 Examples: 132 --- 133 interface Dummy 134 { 135 void test(); 136 void test(int a); 137 } 138 139 auto mock = new Mock!Dummy; 140 mock.test(12); 141 mock.expectNotCalled!"test"(); 142 mock.expectNotCalled!"test"(24); 143 144 // Will throw an exception like "UnitTestException@example.d(6): <test> expected call with <12> <0> count but called <1> counts 145 mock.exceptNotCalled!"test"(12); 146 --- 147 */ 148 void expectNotCalled( string functionName, 149 string message = null, 150 string file = __FILE__, 151 size_t line = __LINE__, 152 V...)(auto ref V values) 153 { 154 expectCalled!(functionName, 0, message, file, line)(values); 155 } 156 157 /** Used to assert function call many counts 158 159 Params: 160 functionName = The name of expected function. 161 count = Count of calls. 162 message = Exception message. 163 file = The file name that the assert failed in. Should be left as default. 164 line = The file line that the assert failed in. Should be left as default. 165 values = Are expected parameter values. 166 167 Throws: 168 If function is not call many count with parameters, will throw an UnitTestException. 169 170 Examples: 171 --- 172 interface Dummy 173 { 174 void test(); 175 void test(int a); 176 } 177 178 auto mock = new Mock!Dummy; 179 mock.test(); 180 mock.test(12); 181 mock.expectCalled!("test", 1)(); 182 mock.expectCalled!("test", 1)(12); 183 184 // Will throw an exception like "UnitTestException@example.d(6): <test> expected call with <12> <2> count but called <1> counts 185 mock.exceptCalled!("test", 2)(12); 186 --- 187 */ 188 void expectCalled(string functionName, 189 ulong count, 190 string message = null, 191 string file = __FILE__, 192 size_t line = __LINE__, 193 V...)(auto ref V values) 194 { 195 string[] callVariants; 196 ulong callCount; 197 198 foreach(i, calledFunctionName; _calledFuncs) 199 { 200 if(calledFunctionName == functionName) 201 { 202 if(tuple(values).to!string() == _calledValues[i]) 203 { 204 callCount++; 205 } 206 else 207 { 208 callVariants ~= _calledValues[i]; 209 } 210 } 211 } 212 213 if(callCount != count) 214 { 215 if(!message) 216 { 217 string[] arguments; 218 foreach(value; values) 219 { 220 arguments ~= to!string(value); 221 } 222 223 if(callCount > 0) 224 { 225 throw new UnitTestException( 226 "<%s> expected call with <%s> <%d> count but called <%d> counts".format(functionName, 227 arguments.join(","), 228 count, 229 callCount), 230 file, 231 line); 232 } 233 else 234 { 235 throw new UnitTestException( 236 "<%s> expected call with <%s> but called with <%s>".format( functionName, 237 arguments.join(","), 238 callVariants.join(",")), 239 file, 240 line); 241 } 242 } 243 else 244 { 245 throw new UnitTestException(message, file, line); 246 } 247 } 248 } 249 250 private: 251 string[] _calledFuncs; 252 string[] _calledValues; 253 } 254 255 alias Identity(alias T) = T; 256 private enum isPrivate(T, string member) = !__traits(compiles, __traits(getMember, T, member)); 257 258 private string generateDefaultImplementationMixin(T)() 259 { 260 import std.array: join; 261 import std.conv: text; 262 import std.format: format; 263 import std.traits: arity, FunctionAttribute, functionAttributes, Parameters; 264 265 string[] code; 266 267 if(!__ctfe) return null; 268 269 // foreach all member in the class 270 foreach(memberName; __traits(allMembers, T)) 271 { 272 // If not private member 273 static if(!isPrivate!(T, memberName)) 274 { 275 alias member = Identity!(__traits(getMember, T, memberName)); 276 // If it is virtual method 277 static if(__traits(isVirtualMethod, member)) 278 { 279 foreach(i, overload; __traits(getOverloads, T, memberName)) 280 { 281 static if(!(functionAttributes!member & FunctionAttribute.const_)) 282 { 283 enum overloadName = text(memberName, "_", i); 284 enum overloadString = `Identity!(__traits(getOverloads, T, "%s")[%s])`.format(memberName, i); 285 code ~= "private alias %s_parameters = Parameters!(%s);".format(overloadName, overloadString); 286 code ~= "private alias %s_returnType = ReturnType!(%s);".format(overloadName, overloadString); 287 288 static if(functionAttributes!member & FunctionAttribute.nothrow_) 289 { 290 enum tryIndent = " "; 291 } 292 else 293 { 294 enum tryIndent = ""; 295 } 296 297 string[] returnDefault; 298 299 static if(typeof(overload).stringof.indexOf("void(") != 0) 300 { 301 enum varName = overloadName ~ `_returnValues`; 302 code ~= `private %s_returnType[] %s;`.format(overloadName, varName); 303 code ~= ""; 304 returnDefault = [ 305 ` if(` ~ varName ~ `.length > 0)`, 306 ` {`, 307 ` auto ret = ` ~ varName ~ `[0];`, 308 ` ` ~ varName ~ ` = ` ~ varName ~ `[1..$];`, 309 ` return ret;`, 310 ` }`, 311 ` else`, 312 ` {`, 313 ` return %s_returnType.init;`.format(overloadName), 314 ` }`]; 315 } 316 317 string[] parts; 318 string[] tupleParts; 319 foreach(j, t; Parameters!overload) 320 { 321 parts ~= "%s_parameters[%s] arg%s".format(overloadName, j, j); 322 tupleParts ~= "arg%s".format(j); 323 } 324 325 code ~= `override ` ~ overloadName ~ "_returnType " ~ memberName ~ "(" ~ parts.join(", ") ~ 326 ")" ~ " " ~ functionAttributesString!member; 327 code ~= "{"; 328 329 static if(functionAttributes!member & FunctionAttribute.nothrow_) 330 { 331 code ~= "try"; 332 code ~= "{"; 333 } 334 335 code ~= tryIndent ~ ` _calledFuncs ~= "` ~ memberName ~ `";`; 336 code ~= tryIndent ~ ` _calledValues ~= tuple` ~ "(" ~ tupleParts.join(", ") ~ ")" ~ `.to!string;`; 337 338 static if(functionAttributes!member & FunctionAttribute.nothrow_) 339 { 340 code ~= "}"; 341 code ~= "catch(Exception) {}"; 342 } 343 344 code ~= returnDefault.join("\n"); 345 code ~= `}`; 346 code ~= ""; 347 } 348 } 349 } 350 } 351 } 352 353 return code.join("\n"); 354 } 355 356 private string functionAttributesString(alias F)() 357 { 358 import std.array: join; 359 import std.traits: FunctionAttribute, functionAttributes; 360 361 string[] parts; 362 const attrs = functionAttributes!F; 363 364 if(attrs & FunctionAttribute.pure_) parts ~= "pure"; 365 if(attrs & FunctionAttribute.nothrow_) parts ~= "nothrow"; 366 if(attrs & FunctionAttribute.trusted) parts ~= "@trusted"; 367 if(attrs & FunctionAttribute.safe) parts ~= "@safe"; 368 if(attrs & FunctionAttribute.nogc) parts ~= "@nogc"; 369 if(attrs & FunctionAttribute.system) parts ~= "@system"; 370 if(attrs & FunctionAttribute.shared_) parts ~= "shared"; 371 372 return parts.join(" "); 373 }