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 @("because defines reason for assertion") 148 unittest 149 { 150 2.should.be(5).because("string A") 151 .should.throwA!FluentException.where.reason.should.equal("string A"); 152 } 153 154 @("compares messages in throwA string overload") 155 unittest 156 { 157 2.should.be(5).because("string A") 158 .should.throwA!FluentException("string B") 159 .should.throwA!FluentException; 160 } 161 162 @("prints informative errors for int comparison") 163 unittest 164 { 165 2.should.be(3).should.throwA!FluentException("Test failed: expected value == 3, but got 2"); 166 } 167 168 @("prints informative errors for object comparison") 169 unittest 170 { 171 Object obj; 172 173 obj.should.not.be(null) 174 .should.throwA!FluentException("Test failed: expected non-null, but got null"); 175 176 obj = new Object; 177 178 obj.should.be(null) 179 .should.throwA!FluentException("Test failed: expected null, but got object.Object"); 180 181 obj.should.not.be.same.as(obj) 182 .should.throwA!FluentException( 183 "Test failed: expected different reference than object.Object, but got same reference"); 184 185 obj.should.be.same.as(new Object) 186 .should.throwA!FluentException( 187 "Test failed: expected same reference as object.Object, but got object.Object"); 188 } 189 190 @("prints informative errors for inequalities") 191 unittest 192 { 193 2.should.be.greater.equal(5) 194 .should.throwA!FluentException("Test failed: expected value >= 5, but got 2"); 195 196 2.should.not.be.less.equal(5) 197 .should.throwA!FluentException("Test failed: expected value not <= 5, but got 2"); 198 } 199 200 @("prints informative errors for range emptiness") 201 unittest 202 { 203 (int[]).init.should.not.be.empty 204 .should.throwA!FluentException("Test failed: expected nonempty range, but got []"); 205 206 [5].should.be.empty 207 .should.throwA!FluentException("Test failed: expected empty range, but got [5]"); 208 } 209 210 @("prettyprints json values for comparison") 211 unittest 212 { 213 import std.json : parseJSON; 214 215 const expected = `Test failed: expected ` ~ ` 216 { 217 ` ~ red(`- "b": "Bar"`) ~ ` 218 }, but got ` ~ ` 219 { 220 ` ~ green(`+ "a": "Foo"`) ~ ` 221 }`; 222 223 const left = parseJSON(`{"a": "Foo"}`); 224 const right = parseJSON(`{"b": "Bar"}`); 225 226 left.should.equal(right) 227 .should.throwA!FluentException(expected); 228 left.should.be(right) 229 .should.throwA!FluentException(expected); 230 } 231 232 @("prints informative errors for approximate checks") 233 unittest 234 { 235 2.should.approximately.be(4, error=0.5) 236 .should.throwA!FluentException("Test failed: expected 4 ± 0.5, but got 2"); 237 238 (2.4).should.not.approximately.be(2, error=0.5) 239 .should.throwA!FluentException("Test failed: expected value outside 2 ± 0.5, but got 2.4"); 240 } 241 242 @("asserts when forgetting to terminate should expression") 243 unittest 244 { 245 void test() 246 { 247 2.should; 248 } 249 250 test.should.throwAn!Exception("unterminated should-chain!"); 251 } 252 253 @("exceptions in the lhs don't set off the unterminated-chain error") 254 unittest 255 { 256 int foo() 257 { 258 throw new Exception(""); 259 } 260 261 foo.should.be(3).should.throwAn!Exception; 262 } 263 264 @("nullable equality") 265 unittest 266 { 267 import std.typecons : Nullable; 268 269 Nullable!int(42).should.not.equal(Nullable!int()); 270 271 Nullable!int(42).should.equal(Nullable!int()).should.throwA!FluentException 272 ("Test failed: expected Nullable.null, but got 42"); 273 } 274 275 @("nullable equality") 276 unittest 277 { 278 import std.typecons : Nullable; 279 280 Nullable!string().should.not.equal(""); 281 282 Nullable!string().should.equal("").should.throwA!FluentException 283 ("Test failed: expected value == '', but got Nullable!string.null"); 284 } 285 286 @("exception thrown by value is not hijacked by unterminated should-chain error") 287 unittest 288 { 289 int foo() 290 { 291 throw new Exception("foo"); 292 } 293 294 foo.should.equal(2).should.throwAn!Exception("foo"); 295 2.should.equal(foo).should.throwAn!Exception("foo"); 296 } 297 298 @("compare two unequal values with the same toString") 299 unittest 300 { 301 class Class 302 { 303 override bool opEquals(const Object other) const 304 { 305 return false; 306 } 307 308 override string toString() const 309 { 310 return "Class"; 311 } 312 } 313 314 auto first = new Class; 315 auto second = new Class; 316 317 (first == second).should.be(false); 318 first.should.equal(second).should.throwAn!Exception("Test failed: expected Class, but got Class"); 319 }