1 module dshould; 2 3 import dshould.ShouldType; 4 5 public import dshould.ShouldType : because, should; 6 public import dshould.basic; 7 public import dshould.contain; 8 public import dshould.empty; 9 public import dshould.stringcmp; 10 public import dshould.thrown; 11 12 // dispatch based on type 13 14 /** 15 * The word `.equal` tests its parameter for equality with the left-hand side. 16 * If the parameters are strings, a colored diff is used. 17 */ 18 public void equal(Should, T)(Should should, T value, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__) 19 if (isInstanceOf!(ShouldType, Should)) 20 { 21 import prettyprint : prettyprint; 22 import std.conv : to; 23 import std.json : JSONValue; 24 import std.traits : Unqual; 25 import std.typecons : No; 26 27 static if (!should.hasWord!"not") 28 { 29 static if (is(typeof(should.got()) == string) && is(T == string)) 30 { 31 auto got = should.got(); 32 const gotString = got.quote; 33 const valueString = value.quote; 34 35 should.terminateChain; 36 } 37 else static if ( 38 is(Unqual!T == JSONValue) 39 && is(Unqual!(typeof(should.got())) == JSONValue)) 40 { 41 should.allowOnlyWords!().before!"equal (string)"; 42 43 auto got = should.got(); 44 const gotString = got.toPrettyString; 45 const valueString = value.toPrettyString; 46 47 should.terminateChain; 48 } 49 else static if ( 50 __traits(compiles, T.init.toString()) 51 && __traits(compiles, typeof(should.got()).init.toString())) 52 { 53 should.allowOnlyWords!().before!"equal (string)"; 54 55 auto got = should.got(); 56 const gotString = got.toString().prettyprint; 57 const valueString = value.toString().prettyprint; 58 59 should.terminateChain; 60 } 61 else static if ( 62 __traits(compiles, T.init[0].toString()) 63 && __traits(compiles, typeof(should.got()).init[0].toString())) 64 { 65 should.allowOnlyWords!().before!"equal (string)"; 66 67 auto got = should.got(); 68 const gotString = got.to!string.prettyprint; 69 const valueString = value.to!string.prettyprint; 70 71 should.terminateChain; 72 } 73 } 74 75 static if (__traits(compiles, got)) 76 { 77 if (got != value) 78 { 79 stringCmpError(gotString, valueString, No.quote, file, line); 80 } 81 } 82 else 83 { 84 dshould.basic.equal(should, value, Fence(), file, line); 85 } 86 } 87 88 public auto equal(Should)(Should should) 89 if (isInstanceOf!(ShouldType, Should)) 90 { 91 return dshould.basic.equal(should); 92 } 93 94 /// be is equivalent to equal for the .should.be(value) case 95 public void be(Should, T)(Should should, T value, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__) 96 if (isInstanceOf!(ShouldType, Should)) 97 { 98 static if (should.hasNoWords && !is(T == typeof(null))) 99 { 100 return equal(should, value, Fence(), file, line); 101 } 102 else 103 { 104 // be is basic.be for all other cases 105 return dshould.basic.be(should, value, Fence(), file, line); 106 } 107 } 108 109 // be is basic.be for all other cases 110 public auto be(Should)(Should should) 111 if (isInstanceOf!(ShouldType, Should)) 112 { 113 return dshould.basic.be(should); 114 } 115 116 // be is basic.be for all other cases 117 public auto be(Should, T)( 118 Should should, T expected, ErrorValue error, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__) 119 if (isInstanceOf!(ShouldType, Should) && should.hasWord!"approximately") 120 { 121 return dshould.basic.be(should, expected, error, Fence(), file, line); 122 } 123 124 /** 125 * The word `.throwA` (or `.throwAn`) expects its left-hand side expression to throw an exception. 126 * Instead of the cumbersome `.where.msg.should.equal("msg")`, the `msg` of the Exception to expect 127 * can be passed directly. 128 */ 129 public template throwA(T : Throwable) 130 { 131 void throwA(Should)(Should should, string msgTest, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__) 132 if (isInstanceOf!(ShouldType, Should)) 133 { 134 dshould.thrown.throwA!T(should, Fence(), file, line).where.msg.should.equal(msgTest, Fence(), file, line); 135 } 136 137 auto throwA(Should)(Should should, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__) 138 if (isInstanceOf!(ShouldType, Should)) 139 { 140 return dshould.thrown.throwA!T(should, Fence(), file, line); 141 } 142 } 143 144 /// ditto 145 public alias throwAn = throwA; 146 147 @("equal is pure @safe") 148 pure @safe unittest 149 { 150 import std.datetime : TimeOfDay; 151 152 "Hello World".should.equal("Hello World"); 153 TimeOfDay(1, 2, 3).should.equal(TimeOfDay(1, 2, 3)); 154 } 155 156 @("because defines reason for assertion") 157 unittest 158 { 159 2.should.be(5).because("string A") 160 .should.throwA!FluentException.where.reason.should.equal("string A"); 161 } 162 163 @("compares messages in throwA string overload") 164 unittest 165 { 166 2.should.be(5).because("string A") 167 .should.throwA!FluentException("string B") 168 .should.throwA!FluentException; 169 } 170 171 @("prints informative errors for int comparison") 172 unittest 173 { 174 2.should.be(3).should.throwA!FluentException("Test failed: expected value == 3, but got 2"); 175 } 176 177 @("prints informative errors for object comparison") 178 unittest 179 { 180 Object obj; 181 182 obj.should.not.be(null) 183 .should.throwA!FluentException("Test failed: expected non-null, but got null"); 184 185 obj = new Object; 186 187 obj.should.be(null) 188 .should.throwA!FluentException("Test failed: expected null, but got object.Object"); 189 190 obj.should.not.be.same.as(obj) 191 .should.throwA!FluentException( 192 "Test failed: expected different reference than object.Object, but got same reference"); 193 194 obj.should.be.same.as(new Object) 195 .should.throwA!FluentException( 196 "Test failed: expected same reference as object.Object, but got object.Object"); 197 } 198 199 @("prints informative errors for inequalities") 200 unittest 201 { 202 2.should.be.greater.equal(5) 203 .should.throwA!FluentException("Test failed: expected value >= 5, but got 2"); 204 205 2.should.not.be.less.equal(5) 206 .should.throwA!FluentException("Test failed: expected value not <= 5, but got 2"); 207 } 208 209 @("prints informative errors for range emptiness") 210 unittest 211 { 212 (int[]).init.should.not.be.empty 213 .should.throwA!FluentException("Test failed: expected nonempty range, but got []"); 214 215 [5].should.be.empty 216 .should.throwA!FluentException("Test failed: expected empty range, but got [5]"); 217 } 218 219 @("prettyprints json values for comparison") 220 unittest 221 { 222 import std.json : parseJSON; 223 224 const expected = `Test failed: expected ` ~ ` 225 { 226 ` ~ red(`- "b": "Bar"`) ~ ` 227 }, but got ` ~ ` 228 { 229 ` ~ green(`+ "a": "Foo"`) ~ ` 230 }`; 231 232 const left = parseJSON(`{"a": "Foo"}`); 233 const right = parseJSON(`{"b": "Bar"}`); 234 235 left.should.equal(right) 236 .should.throwA!FluentException(expected); 237 left.should.be(right) 238 .should.throwA!FluentException(expected); 239 } 240 241 @("prints informative errors for approximate checks") 242 unittest 243 { 244 2.should.approximately.be(4, error=0.5) 245 .should.throwA!FluentException("Test failed: expected 4 ± 0.5, but got 2"); 246 247 (2.4).should.not.approximately.be(2, error=0.5) 248 .should.throwA!FluentException("Test failed: expected value outside 2 ± 0.5, but got 2.4"); 249 } 250 251 @("asserts when forgetting to terminate should expression") 252 unittest 253 { 254 void test() 255 { 256 2.should; 257 } 258 259 test.should.throwAn!Exception("unterminated should-chain!"); 260 } 261 262 @("exceptions in the lhs don't set off the unterminated-chain error") 263 unittest 264 { 265 int foo() 266 { 267 throw new Exception(""); 268 } 269 270 foo.should.be(3).should.throwAn!Exception; 271 } 272 273 @("nullable equality") 274 unittest 275 { 276 import std.typecons : Nullable; 277 278 Nullable!int(42).should.not.equal(Nullable!int()); 279 280 Nullable!int(42).should.equal(Nullable!int()).should.throwA!FluentException 281 ("Test failed: expected Nullable.null, but got 42"); 282 } 283 284 @("nullable equality") 285 unittest 286 { 287 import std.typecons : Nullable; 288 289 Nullable!string().should.not.equal(""); 290 291 Nullable!string().should.equal("").should.throwA!FluentException 292 ("Test failed: expected value == '', but got Nullable!string.null"); 293 } 294 295 @("exception thrown by value is not hijacked by unterminated should-chain error") 296 unittest 297 { 298 int foo() 299 { 300 throw new Exception("foo"); 301 } 302 303 foo.should.equal(2).should.throwAn!Exception("foo"); 304 2.should.equal(foo).should.throwAn!Exception("foo"); 305 } 306 307 @("compare two unequal values with the same toString") 308 unittest 309 { 310 class Class 311 { 312 override bool opEquals(const Object other) const 313 { 314 return false; 315 } 316 317 override string toString() const 318 { 319 return "Class"; 320 } 321 } 322 323 auto first = new Class; 324 auto second = new Class; 325 326 (first == second).should.be(false); 327 first.should.equal(second).should.throwAn!Exception("Test failed: expected Class, but got Class"); 328 }