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 import core.exception : AssertError; 169 170 void test() 171 { 172 2.should; 173 } 174 175 test.should.throwAn!AssertError("unterminated should-chain!"); 176 } 177 178 @("exceptions in the lhs don't set off the unterminated-chain error") 179 unittest 180 { 181 int foo() 182 { 183 throw new Exception(""); 184 } 185 186 foo.should.be(3).should.throwAn!Exception; 187 } 188 189 @("nullable equality") 190 unittest 191 { 192 import std.typecons : Nullable; 193 194 Nullable!int(42).should.not.equal(Nullable!int()); 195 196 Nullable!int(42).should.equal(Nullable!int()).should.throwA!FluentException 197 ("Test failed: expected value == Nullable!int.null, but got 42"); 198 } 199 200 @("nullable equality") 201 unittest 202 { 203 import std.typecons : Nullable; 204 205 Nullable!string().should.not.equal(""); 206 207 Nullable!string().should.equal("").should.throwA!FluentException 208 ("Test failed: expected value == '', but got Nullable!string.null"); 209 }