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