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 std.json : JSONValue; 22 import std.traits : Unqual; 23 24 static if (is(typeof(should.got()) == string) && is(T == string) && !should.hasWord!"not") 25 { 26 dshould.stringcmp.equal(should, value, Fence(), file, line); 27 } 28 else static if ( 29 is(Unqual!T == JSONValue) 30 && is(Unqual!(typeof(should.got())) == JSONValue) 31 && !should.hasWord!"not" 32 ) 33 { 34 should.terminateChain; 35 should.got().toPrettyString.should.equal(value.toPrettyString, Fence(), file, line); 36 } 37 else 38 { 39 dshould.basic.equal(should, value, Fence(), file, line); 40 } 41 } 42 43 public auto equal(Should)(Should should) 44 if (isInstanceOf!(ShouldType, Should)) 45 { 46 return dshould.basic.equal(should); 47 } 48 49 /** 50 * The word `.throwA` (or `.throwAn`) expects its left-hand side expression to throw an exception. 51 * Instead of the cumbersome `.where.msg.should.equal("msg")`, the `msg` of the Exception to expect 52 * can be passed directly. 53 */ 54 public template throwA(T : Throwable) 55 { 56 void throwA(Should)(Should should, string msgTest, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__) 57 if (isInstanceOf!(ShouldType, Should)) 58 { 59 dshould.thrown.throwA!T(should, Fence(), file, line).where.msg.should.equal(msgTest, Fence(), file, line); 60 } 61 62 auto throwA(Should)(Should should, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__) 63 if (isInstanceOf!(ShouldType, Should)) 64 { 65 return dshould.thrown.throwA!T(should, Fence(), file, line); 66 } 67 } 68 69 /// ditto 70 public alias throwAn = throwA; 71 72 @("because defines reason for assertion") 73 unittest 74 { 75 2.should.be(5).because("string A") 76 .should.throwA!FluentException.where.reason.should.equal("string A"); 77 } 78 79 @("compares messages in throwA string overload") 80 unittest 81 { 82 2.should.be(5).because("string A") 83 .should.throwA!FluentException("string B") 84 .should.throwA!FluentException; 85 } 86 87 @("prints informative errors for int comparison") 88 unittest 89 { 90 2.should.be(3).should.throwA!FluentException("Test failed: expected value == 3, but got 2"); 91 } 92 93 @("prints informative errors for object comparison") 94 unittest 95 { 96 Object obj; 97 98 obj.should.not.be(null) 99 .should.throwA!FluentException("Test failed: expected non-null, but got null"); 100 101 obj = new Object; 102 103 obj.should.be(null) 104 .should.throwA!FluentException("Test failed: expected null, but got object.Object"); 105 106 obj.should.not.be.same.as(obj) 107 .should.throwA!FluentException( 108 "Test failed: expected different reference than object.Object, but got same reference"); 109 110 obj.should.be.same.as(new Object) 111 .should.throwA!FluentException( 112 "Test failed: expected same reference as object.Object, but got object.Object"); 113 } 114 115 @("prints informative errors for inequalities") 116 unittest 117 { 118 2.should.be.greater.equal(5) 119 .should.throwA!FluentException("Test failed: expected value >= 5, but got 2"); 120 121 2.should.not.be.less.equal(5) 122 .should.throwA!FluentException("Test failed: expected value not <= 5, but got 2"); 123 } 124 125 @("prints informative errors for range emptiness") 126 unittest 127 { 128 (int[]).init.should.not.be.empty 129 .should.throwA!FluentException("Test failed: expected nonempty range, but got []"); 130 131 [5].should.be.empty 132 .should.throwA!FluentException("Test failed: expected empty range, but got [5]"); 133 } 134 135 @("prettyprints json values for comparison") 136 unittest 137 { 138 import std.json : parseJSON; 139 140 const expected = `Test failed: expected ' { 141 ` ~ red(`- "b": "Bar"`) ~ ` 142 } 143 ', but got ' 144 { 145 ` ~ green(`+ "a": "Foo"`) ~ ` 146 }'`; 147 148 const left = parseJSON(`{"a": "Foo"}`); 149 const right = parseJSON(`{"b": "Bar"}`); 150 151 left.should.equal(right) 152 .should.throwA!FluentException(expected); 153 } 154 155 @("prints informative errors for approximate checks") 156 unittest 157 { 158 2.should.approximately.be(4, error=0.5) 159 .should.throwA!FluentException("Test failed: expected 4 ± 0.5, but got 2"); 160 161 (2.4).should.not.approximately.be(2, error=0.5) 162 .should.throwA!FluentException("Test failed: expected value outside 2 ± 0.5, but got 2.4"); 163 } 164 165 @("asserts when forgetting to terminate should expression") 166 unittest 167 { 168 void test() 169 { 170 2.should; 171 } 172 173 test.should.throwAn!Exception("unterminated should-chain!"); 174 } 175 176 @("exceptions in the lhs don't set off the unterminated-chain error") 177 unittest 178 { 179 int foo() 180 { 181 throw new Exception(""); 182 } 183 184 foo.should.be(3).should.throwAn!Exception; 185 } 186 187 @("nullable equality") 188 unittest 189 { 190 import std.typecons : Nullable; 191 192 Nullable!int(42).should.not.equal(Nullable!int()); 193 194 Nullable!int(42).should.equal(Nullable!int()).should.throwA!FluentException 195 ("Test failed: expected value == Nullable!int.null, but got 42"); 196 } 197 198 @("nullable equality") 199 unittest 200 { 201 import std.typecons : Nullable; 202 203 Nullable!string().should.not.equal(""); 204 205 Nullable!string().should.equal("").should.throwA!FluentException 206 ("Test failed: expected value == '', but got Nullable!string.null"); 207 } 208 209 @("exception thrown by value is not hijacked by unterminated should-chain error") 210 unittest 211 { 212 int foo() 213 { 214 throw new Exception("foo"); 215 } 216 217 foo.should.equal(2).should.throwAn!Exception("foo"); 218 2.should.equal(foo).should.throwAn!Exception("foo"); 219 }