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 }