├── .gitignore
├── LICENSE
├── README.md
├── docs
└── dunit
│ ├── error.html
│ ├── mockable.html
│ ├── output
│ └── console.html
│ ├── reflection.html
│ ├── result
│ ├── moduleresult.html
│ └── moduleresultcollection.html
│ └── toolkit.html
├── dub.json
└── source
├── dunit
├── error.d
├── mockable.d
├── moduleunittester.d
├── output
│ └── console.d
├── reflection.d
├── result
│ ├── moduleresult.d
│ └── moduleresultcollection.d
└── toolkit.d
└── example.d
/.gitignore:
--------------------------------------------------------------------------------
1 | .project
2 | docs/__main.html
3 | docs/example.html
4 | docs/dunit/moduleunittester.html
5 | dub.selections.json
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Gary Willoughby
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DUnit
2 | **Advanced unit testing toolkit for the D programming language**
3 |
4 | [](https://travis-ci.org/nomad-software/dunit)
5 |
6 | ---
7 |
8 | DUnit is a unit testing toolkit for the D programming language. The toolkit comprises of a solution to mocking objects and a library to enable more expressive and helpful assertions.
9 |
10 | Unit testing is necessary to assert *units* of code perform in isolation and conform to repeatable and known expectations. DUnit gives you the tools to make this task an easier one.
11 |
12 | ## Supported platforms
13 | DUnit was developed and tested with the latest DMD compiler and should support any platform DMD supports as it only contains platform independent code. Other compilers have not been tested but should build fine.
14 |
15 | ## Features
16 |
17 | ### Object mocking
18 | DUnit features a mixin template to inject mockable behaviour into a class or interface. Once injected, a static method allows you to retrieve an object with the same properties as the class or interface it was retrieved from. Mock objects behave and act as their parent (or in the case of interfaces an implemention) but with the added feature that all methods can be replaced by a delegate at runtime*.
19 |
20 | * Final methods cannot be replaced.
21 | * The mixin only injects code when the `-unittest` compiler flag is used.
22 |
23 | ### Helpful asserts
24 | When using DUnit the built-in unit test handler has been replaced to give more helpful error messages. When something goes wrong the error tries to be as helpful as possible by showing file, line, and assert value output.
25 |
26 | DUnit by Gary Willoughby.
27 | > Running unit tests
28 | - example
29 |
30 | +----------------------------------------------------------------------
31 | | Failed asserting equal
32 | +----------------------------------------------------------------------
33 | | File: example.d
34 | | Line: 91
35 | +----------------------------------------------------------------------
36 | | ✓ Expected value: (int) 1
37 | | ✗ Actual value: (ulong) 2
38 |
39 | ## Compiler flags
40 |
41 | ### Required
42 | 1. Mocking behaviour is only injected in unit test mode so you must used the `-unittest` flag for mocking to work.
43 | 1. Usually when using DUnit all unit testing code is placed within unittest blocks. If this is the case you must compile using the `-unittest` flag to enable their execution.
44 |
45 | ### Notes
46 | 1. When compiling using the `-unittest` switch be mindful that standard assert functions will exit the program and display an error, even when compiled with the `-release` switch! To stop this happening, remove the `-unittest` switch from the compile command and `-release` will once again ignore asserts.
47 |
48 | ## Documentation
49 | There is full HTML documentation within the repository inside the [docs](https://github.com/nomad-software/dunit/tree/master/docs) directory.
50 |
51 | ### Building with dub
52 | To build DUnit as a static library using [dub](https://github.com/rejectedsoftware/dub) use the following command.
53 |
54 | dub build --build=unittest --config=library
55 |
56 | ### View online
57 | - [Mockable mixin](http://htmlpreview.github.io/?https://github.com/nomad-software/dunit/master/docs/dunit/mockable.html)
58 | - [Assert toolkit](http://htmlpreview.github.io/?https://github.com/nomad-software/dunit/master/docs/dunit/toolkit.html)
59 |
60 | ## Example
61 |
62 | [Click here to see an example of how Dunit is used.](https://github.com/nomad-software/dunit/blob/master/source/example.d)
63 |
--------------------------------------------------------------------------------
/docs/dunit/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | dunit.error
10 |
11 |
12 | dunit.error
13 |
14 | Module to handle exceptions.
15 |
16 |
License MIT. See LICENSE for full details.
17 |
18 | class DUnitAssertError : core.exception.AssertError ;
19 |
20 |
An exception thrown when a unit test fails.
21 |
22 |
This exception derives from AssertError to make it possible for
23 | these errors to be thrown from nothrow methods.
24 |
25 |
pure nothrow @safe this(string message, string file, size_t line);
26 |
27 |
Constructor.
28 |
29 |
Parameters string message
30 | The error message.
31 | string file
32 | The file where the error occurred.
33 | size_t line
34 | The line where the error occurred.
35 |
36 |
37 |
38 |
@property string[] log ();
39 |
40 |
Return the exception log .
41 |
42 |
Return Value The error's logged info, expectations and error messages.
43 |
44 |
45 |
void addInfo (T)(string caption, T value, string icon = "\xe2\x84\xb9");
46 |
47 |
Add a line of info to the exception log.
48 |
49 |
Parameters string caption
50 | The caption.
51 | T value
52 | The value.
53 | string icon
54 | The icon before the caption.
55 |
56 |
57 |
58 |
void addTypedInfo (T)(string caption, T value, string icon = "\xe2\x84\xb9");
59 |
60 |
Add a line of typed info to the exception log.
61 |
62 |
Parameters string caption
63 | The caption.
64 | T value
65 | The value.
66 | string icon
67 | The icon before the caption.
68 |
69 |
70 |
71 |
void addExpectation (T)(string caption, T value, string icon = "\xe2\x9c\x93");
72 |
73 |
Add a line of expected info to the exception log.
74 |
75 |
Parameters string caption
76 | The caption.
77 | T value
78 | The value.
79 | string icon
80 | The icon before the caption.
81 |
82 |
83 |
84 |
void addTypedExpectation (T)(string caption, T value, string icon = "\xe2\x9c\x93");
85 |
86 |
Add a line of typed expected info to the exception log.
87 |
88 |
Parameters string caption
89 | The caption.
90 | T value
91 | The value.
92 | string icon
93 | The icon before the caption.
94 |
95 |
96 |
97 |
void addError (T)(string caption, T value, string icon = "\xe2\x9c\x97");
98 |
99 |
Add a line of error info to the exception log.
100 |
101 |
Parameters string caption
102 | The caption.
103 | T value
104 | The value.
105 | string icon
106 | The icon before the caption.
107 |
108 |
109 |
110 |
void addTypedError (T)(string caption, T value, string icon = "\xe2\x9c\x97");
111 |
112 |
Add a line of typed error info to the exception log.
113 |
114 |
Parameters string caption
115 | The caption.
116 | T value
117 | The value.
118 | string icon
119 | The icon before the caption.
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/docs/dunit/mockable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | dunit.mockable
10 |
11 |
12 | dunit.mockable
13 |
14 | Mixin template to enable mocking.
15 |
16 |
Many methods implement compile-time parameters (file, line) that are set at the call site.
17 | It is preferred that these parameters are ignored when using these methods.
18 |
19 |
20 |
License MIT. See LICENSE for full details.
21 |
22 | template Mockable (C) if (is(C == class) || is(C == interface))
23 |
A template mixin used to inject code into a class to provide mockable behaviour.
24 | Code is nested within the host class to provide access to all host types.
25 | Only injects code when using the -unittest compiler switch.
26 |
27 |
Caveats:
28 | Only module level types can be made mockable.
29 |
30 |
31 |
Example:
32 |
import dunit.mockable;
33 |
34 | class T
35 | {
36 | mixin Mockable !(T);
37 | }
38 |
39 |
40 |
41 |
auto getMock (A...)(A args);
42 |
43 |
Injected by the Mockable mixin template this method allows creation of mock object instances of the mockable class.
44 |
45 |
Parameters A args
46 | The constructor arguments of the host class.
47 |
48 |
Example:
49 |
import dunit.mockable;
50 |
51 | class T
52 | {
53 | mixin Mockable!(T);
54 | }
55 |
56 | unittest
57 | {
58 | import dunit.toolkit;
59 |
60 | auto mock = T.getMock ();
61 |
62 | assertTrue(cast (T)mock); }
64 |
65 |
66 |
67 |
Templates:
68 | Templated classes are supported when creating mocks.
69 | Simply include the template parameters for the class when calling the '
getMock ' method.
70 |
auto mock = T!(int ).getMock ();
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/docs/dunit/output/console.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | dunit.output.console
10 |
11 |
12 | dunit.output.console
13 |
14 | Module to handle output to the console .
15 |
16 |
License MIT. See LICENSE for full details.
17 |
18 | class Console ;
19 |
20 |
Format output to the console.
21 |
22 |
void write (string line);
23 |
24 |
Write a line to the console.
25 |
26 |
Parameters string line
27 | The line to write .
28 |
29 |
30 |
31 |
void write (string line, int indent);
32 |
33 |
Write an indented line to the console.
34 |
35 |
Parameters string line
36 | The line to write .
37 | int indent
38 | The space indent before the line.
39 |
40 |
41 |
42 |
void write (string prefix, string line);
43 |
44 |
Write a prefixed line to the console.
45 |
46 |
Parameters string prefix
47 | The prefix of the line.
48 | string line
49 | The line to write .
50 |
51 |
52 |
53 |
void write (string prefix, string line, int indent);
54 |
55 |
Write an intented, prefixed line to the console.
56 |
57 |
Parameters string prefix
58 | The prefix of the line.
59 | string line
60 | The line to write .
61 | int indent
62 | The space indent before the line.
63 |
64 |
65 |
66 |
void writeHeader ();
67 |
68 |
71 |
void writeReport (ModuleResultCollection results);
72 |
73 |
Output a detailed report.
74 |
75 |
Parameters ModuleResultCollection results
76 | A module result collection.
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/docs/dunit/reflection.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | dunit.reflection
10 |
11 |
12 | dunit.reflection
13 |
14 | Internal reflection templates for implementing mocking behaviour.
15 |
16 |
License MIT. See LICENSE for full details.
17 |
18 | template DUnitMethodIterator (T, string generator, int index = 0) if (is(T == class) || is(T == interface))
19 |
Iterate through the methods of T generating code using the generator.
20 |
21 |
Parameters T
22 | The class to inspect.
23 | generator
24 | The template to use to generate code for each method.
25 | index
26 | The beginning index of the members.
27 |
28 |
29 |
30 |
template DUnitConstructorIterator (T, string generator) if (is(T == class))
31 |
Iterate through the constructors of T generating code using the generator.
32 |
33 |
Parameters T
34 | The class to inspect.
35 | generator
36 | The template to use to generate code for each constructor.
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/docs/dunit/result/moduleresult.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | dunit.result.moduleresult
10 |
11 |
12 | dunit.result.moduleresult
13 |
14 | Module to contain the module result class.
15 |
16 |
License MIT. See LICENSE for full details.
17 |
18 | class ModuleResult ;
19 |
20 |
A class to contain the result from running a module's unit tests.
21 |
22 |
this(string name, DUnitAssertError error = null);
23 |
24 |
Constructor.
25 |
26 |
Parameters string name
27 | The name of the module who's unit tests where run.
28 | DUnitAssertError error
29 | An error, if it occurred.
30 |
31 |
32 |
33 |
@property string source ();
34 |
35 |
Access the name of the module.
36 |
37 |
Return Value The name of the module that produced this result.
38 |
39 |
40 |
@property DUnitAssertError error ();
41 |
42 |
Access the error if one occurred.
43 |
44 |
Return Value The error if one occurred, null if not.
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/docs/dunit/result/moduleresultcollection.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | dunit.result.moduleresultcollection
10 |
11 |
12 | dunit.result.moduleresultcollection
13 |
14 | Module for the module result collection.
15 |
16 |
License MIT. See LICENSE for full details.
17 |
18 | class ModuleResultCollection ;
19 |
20 |
A class to hold module results.
21 |
22 |
bool empty ();
23 |
24 |
Indicate if the collection is empty .
25 |
26 |
Return Value true if the collection is empty , false if not.
27 |
28 |
29 |
size_t totalCount ();
30 |
31 |
The total number of tests in the collection.
32 |
33 |
Return Value the number of tests that dunit has run.
34 |
35 |
36 |
size_t failedCount ();
37 |
38 |
The amount of tests that contain a DUnitAssertError.
39 |
40 |
Return Value the number of tests that have failed.
41 |
42 |
43 |
size_t passedCount ();
44 |
45 |
The amount of tests that don't contain a DUnitAssertError.
46 |
47 |
Return Value the number of tests that have passed.
48 |
49 |
50 |
void add (ModuleResult result);
51 |
52 |
Add a result to the collection.
53 |
54 |
This method also sorts the collection by source and makes sure all results containing errors are at the end.
55 | This enables the console output to be more user friendly.
56 |
57 |
58 |
Parameters ModuleResult result
59 | The module result to add .
60 |
61 |
62 |
63 |
ModuleResult[] opSlice ();
64 |
65 |
Overload slicing.
66 |
67 |
Return Value The internal collection of module results.
68 |
69 |
70 |
ModuleResult opIndex (size_t index);
71 |
72 |
Overload indexing.
73 |
74 |
Parameters size_t index
75 | The index of the collection.
76 |
77 |
Return Value The module result residing at the passed index.
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/docs/dunit/toolkit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | dunit.toolkit
10 |
11 |
12 | dunit.toolkit
13 |
14 | Assert toolkit for more expressive unit testing.
15 |
16 |
Many methods implement compile-time parameters (file, line) that are set at the call site.
17 | It is preferred that these parameters are ignored when using these methods.
18 |
19 |
20 |
License MIT. See LICENSE for full details.
21 |
22 | void assertApprox (A, B)(A value, B target, long ulps = 10, string message = "Failed asserting approximately equal", string file = __FILE__, size_t line = __LINE__) if (isFloatingPoint!(CommonType!(A, B)));
23 |
24 |
Assert that two floating point values are approximately equal.
25 |
26 |
See Also http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
27 |
28 |
29 |
Parameters A value
30 | The value used during the assertion.
31 | B target
32 | The target value.
33 | long ulps
34 | The maximum space between two approximately equal floating point numbers measured in units of least precision . A higher number means more approximation.
35 | string message
36 | The error message to display.
37 | string file
38 | The file name where the error occurred. The value is added automatically at the call site.
39 | size_t line
40 | The line where the error occurred. The value is added automatically at the call site.
41 |
42 |
Exceptions Thrown DUnitAssertError if the assertation fails.
43 |
Examples
44 | float smallestFloatSubnormal = float .min_normal * float .epsilon;
45 | smallestFloatSubnormal.assertApprox (-smallestFloatSubnormal);
46 |
47 | double smallestDoubleSubnormal = double .min_normal * double .epsilon;
48 | smallestDoubleSubnormal.assertApprox (-smallestDoubleSubnormal);
49 |
50 | 0.0f.assertApprox (-0.0f);
51 | (-0.0f).assertApprox (0.0f);
52 | 0.0.assertApprox (-0.0);
53 | (-0.0).assertApprox (0.0);
54 | 2.0f.assertApprox (1.999999f);
55 | 1.999999f.assertApprox (2.0f);
56 | 2.0.assertApprox (1.999999999999999);
57 | 1.999999999999999.assertApprox (2.0);
58 |
59 | float .max.assertApprox (float .infinity);
61 | float .infinity.assertApprox (float .max);
62 | double .infinity.assertApprox (double .max);
63 | double .max.assertApprox (double .infinity);
64 | float .nan.assertApprox (float .nan);
65 | double .nan.assertApprox (double .nan);
66 |
67 | 10f.assertApprox (0f).assertThrow!(DUnitAssertError)("Failed asserting approximately equal" );
69 |
70 |
71 |
72 |
void assertApprox (A, B)(A value, B target, double epsilon, string message = "Failed asserting approximately equal", string file = __FILE__, size_t line = __LINE__) if (isFloatingPoint!(CommonType!(A, B)));
73 |
74 |
Assert that two floating point values are approximately equal using an epsilon value.
75 |
76 |
See Also http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
77 |
78 |
79 |
Parameters A value
80 | The value used during the assertion.
81 | B target
82 | The target value.
83 | double epsilon
84 | An epsilon value to be used as the maximum absolute and relative error in the comparison.
85 | string message
86 | The error message to display.
87 | string file
88 | The file name where the error occurred. The value is added automatically at the call site.
89 | size_t line
90 | The line where the error occurred. The value is added automatically at the call site.
91 |
92 |
Exceptions Thrown DUnitAssertError if the assertation fails.
93 |
Examples
94 | float smallestFloatSubnormal = float .min_normal * float .epsilon ;
95 | smallestFloatSubnormal.assertApprox (-smallestFloatSubnormal, 0.00001);
96 |
97 | double smallestDoubleSubnormal = double .min_normal * double .epsilon ;
98 | smallestDoubleSubnormal.assertApprox (-smallestDoubleSubnormal, 0.00001);
99 |
100 | 0.0f.assertApprox (-0.0f, 0.00001);
101 | (-0.0f).assertApprox (0.0f, 0.00001);
102 | 0.0.assertApprox (-0.0, 0.00001);
103 | (-0.0).assertApprox (0.0, 0.00001);
104 | 2.0f.assertApprox (1.99f, 0.01);
105 | 1.99f.assertApprox (2.0f, 0.01);
106 | 2.0.assertApprox (1.99, 0.01);
107 | 1.99.assertApprox (2.0, 0.01);
108 |
109 | float .max.assertApprox (float .infinity, 0.00001);
111 | float .infinity.assertApprox (float .max, 0.00001);
112 | double .infinity.assertApprox (double .max, 0.00001);
113 | double .max.assertApprox (double .infinity, 0.00001);
114 | float .nan.assertApprox (float .nan, 0.00001);
115 | double .nan.assertApprox (double .nan, 0.00001);
116 |
117 | 10f.assertApprox (0f, 0.00001).assertThrow!(DUnitAssertError)("Failed asserting approximately equal" );
119 |
120 |
121 |
122 |
void assertCount (A)(A array, ulong count, string message = "Failed asserting array count", string file = __FILE__, size_t line = __LINE__) if (isArray!A || isAssociativeArray!A);
123 |
124 |
Assert that an array contains a particular value count.
125 |
126 |
Parameters A array
127 | The array to interogate.
128 | ulong count
129 | The amount of values the array should hold.
130 | string message
131 | The error message to display.
132 | string file
133 | The file name where the error occurred. The value is added automatically at the call site.
134 | size_t line
135 | The line where the error occurred. The value is added automatically at the call site.
136 |
137 |
Exceptions Thrown DUnitAssertError if the assertation fails.
138 |
Examples
139 | int [string] associativeArray;
140 | int [] dynamicArray;
141 | string string_;
142 |
143 | associativeArray.assertCount (0);
144 | dynamicArray.assertCount (0);
145 | string_.assertCount (0);
146 | [].assertCount (0);
147 |
148 | "Hello" .assertCount (5);
149 | [1, 2, 3, 4].assertCount (4);
150 | ["foo" , "bar" , "baz" , "qux" ].assertCount (4);
151 | [["foo" , "bar" ], ["baz" , "qux" ]].assertCount (2);
152 | ["foo" :1, "bar" :2, "baz" :3, "qux" :4].assertCount (4);
153 |
154 | associativeArray.assertCount (1).assertThrow!(DUnitAssertError)("Failed asserting array count" );
156 |
157 |
158 |
159 |
void assertEmpty (A)(A array, string message = "Failed asserting empty array", string file = __FILE__, size_t line = __LINE__) if (isArray!A || isAssociativeArray!A);
160 |
161 |
Assert that an array is empty.
162 |
163 |
Parameters A array
164 | The array to interogate.
165 | string message
166 | The error message to display.
167 | string file
168 | The file name where the error occurred. The value is added automatically at the call site.
169 | size_t line
170 | The line where the error occurred. The value is added automatically at the call site.
171 |
172 |
Exceptions Thrown DUnitAssertError if the assertation fails.
173 |
Examples
174 | int [string] associativeArray;
175 | int [] dynamicArray;
176 | string string_;
177 |
178 | associativeArray.assertEmpty ();
179 | dynamicArray.assertEmpty ();
180 | string_.assertEmpty ();
181 | [].assertEmpty ();
182 |
183 | [1].assertEmpty ().assertThrow!(DUnitAssertError)("Failed asserting empty array" );
185 |
186 |
187 |
188 |
void assertEmpty (R)(R range, string message = "Failed asserting empty range", string file = __FILE__, size_t line = __LINE__) if ((is(R == class) || is(R == struct)) && isInputRange!R);
189 |
190 |
Assert that a range is empty.
191 |
192 |
Parameters R range
193 | The range to interogate.
194 | string message
195 | The error message to display.
196 | string file
197 | The file name where the error occurred. The value is added automatically at the call site.
198 | size_t line
199 | The line where the error occurred. The value is added automatically at the call site.
200 |
201 |
Exceptions Thrown DUnitAssertError if the assertation fails.
202 |
Examples
203 | class A
204 | {
205 | void popFront() {};
206 | @property bool empty() { return true ; };
207 | @property int front() { return 0; };
208 | }
209 |
210 | struct B
211 | {
212 | void popFront() {};
213 | enum bool empty = false ;
214 | @property int front() { return 1; };
215 | }
216 |
217 | static assert (isInputRange!(A));
218 | static assert (isInputRange!(B));
219 |
220 | auto emptyRange = new A();
221 | emptyRange.assertEmpty ();
222 |
223 | B infiniteRange = B.init;
224 | infiniteRange.takeNone.assertEmpty ();
225 |
226 | infiniteRange.assertEmpty ().assertThrow!(DUnitAssertError)("Failed asserting empty range" );
228 | infiniteRange.take(10).assertEmpty ().assertThrow!(DUnitAssertError)("Failed asserting empty range" );
229 |
230 |
231 |
232 |
void assertEndsWith (string value, string suffix, string message = "Failed asserting ends with", string file = __FILE__, size_t line = __LINE__);
233 |
234 |
Assert that a string ends with a particular string.
235 |
236 |
Parameters string value
237 | The value used during the assertion.
238 | string suffix
239 | The suffix to match.
240 | string message
241 | The error message to display.
242 | string file
243 | The file name where the error occurred. The value is added automatically at the call site.
244 | size_t line
245 | The line where the error occurred. The value is added automatically at the call site.
246 |
247 |
Exceptions Thrown DUnitAssertError if the assertation fails.
248 |
Examples
249 | "foo bar" .assertEndsWith ("bar" );
250 | "baz qux" .assertEndsWith ("qux" );
251 |
252 | "foo" .assertEndsWith ("bar" ).assertThrow!(DUnitAssertError)("Failed asserting ends with" );
254 |
255 |
256 |
257 |
void assertEqual (A, B)(A value, B target, string message = "Failed asserting equal", string file = __FILE__, size_t line = __LINE__);
258 |
259 |
Assert that two values are equal.
260 |
261 |
Parameters A value
262 | The value used during the assertion.
263 | B target
264 | The target value.
265 | string message
266 | The error message to display.
267 | string file
268 | The file name where the error occurred. The value is added automatically at the call site.
269 | size_t line
270 | The line where the error occurred. The value is added automatically at the call site.
271 |
272 |
Exceptions Thrown DUnitAssertError if the assertation fails.
273 |
Examples
274 | 123.assertEqual (123);
275 | "hello" .assertEqual ("hello" );
276 |
277 | 1.assertEqual (2).assertThrow!(DUnitAssertError)("Failed asserting equal" );
279 |
280 |
281 |
282 |
void assertFalse (T)(T value, string message = "Failed asserting false", string file = __FILE__, size_t line = __LINE__);
283 |
284 |
Assert that a boolean value is false .
285 |
286 |
Parameters T value
287 | The value used during the assertion.
288 | string message
289 | The error message to display.
290 | string file
291 | The file name where the error occurred. The value is added automatically at the call site.
292 | size_t line
293 | The line where the error occurred. The value is added automatically at the call site.
294 |
295 |
Exceptions Thrown DUnitAssertError if the assertation fails.
296 |
Examples
297 | false .assertFalse ();
298 |
299 | true .assertFalse ().assertThrow!(DUnitAssertError)("Failed asserting false" );
301 |
302 |
303 |
304 |
void assertFalsey (T)(T value, string message = "Failed asserting falsey", string file = __FILE__, size_t line = __LINE__);
305 |
306 |
Assert that a value evaluates as false .
307 |
308 |
Parameters T value
309 | The value used during the assertion.
310 | string message
311 | The error message to display.
312 | string file
313 | The file name where the error occurred. The value is added automatically at the call site.
314 | size_t line
315 | The line where the error occurred. The value is added automatically at the call site.
316 |
317 |
Exceptions Thrown DUnitAssertError if the assertation fails.
318 |
Examples
319 | false .assertFalsey ();
320 | [].assertFalsey ();
321 | null .assertFalsey ();
322 | 0.assertFalsey ();
323 |
324 | true .assertFalsey ().assertThrow!(DUnitAssertError)("Failed asserting falsey" );
326 |
327 |
328 |
329 |
void assertGreaterThan (A, B)(A value, B threshold, string message = "Failed asserting greater than", string file = __FILE__, size_t line = __LINE__);
330 |
331 |
Assert that a value is greater than a threshold value.
332 |
333 |
Parameters A value
334 | The value used during the assertion.
335 | B threshold
336 | The threshold value.
337 | string message
338 | The error message to display.
339 | string file
340 | The file name where the error occurred. The value is added automatically at the call site.
341 | size_t line
342 | The line where the error occurred. The value is added automatically at the call site.
343 |
344 |
Exceptions Thrown DUnitAssertError if the assertation fails.
345 |
Examples
346 | 11.assertGreaterThan (10);
347 |
348 | 11.assertGreaterThan (12).assertThrow!(DUnitAssertError)("Failed asserting greater than" );
350 |
351 |
352 |
353 |
void assertGreaterThanOrEqual (A, B)(A value, B threshold, string message = "Failed asserting greater than or equal", string file = __FILE__, size_t line = __LINE__);
354 |
355 |
Assert that a value is greater than or equal to a threshold value.
356 |
357 |
Parameters A value
358 | The value used during the assertion.
359 | B threshold
360 | The threshold value.
361 | string message
362 | The error message to display.
363 | string file
364 | The file name where the error occurred. The value is added automatically at the call site.
365 | size_t line
366 | The line where the error occurred. The value is added automatically at the call site.
367 |
368 |
Exceptions Thrown DUnitAssertError if the assertation fails.
369 |
Examples
370 | 10.assertGreaterThanOrEqual (10);
371 | 11.assertGreaterThanOrEqual (10);
372 |
373 | 11.assertGreaterThanOrEqual (12).assertThrow!(DUnitAssertError)("Failed asserting greater than or equal" );
375 |
376 |
377 |
378 |
void assertHasKey (A, B)(A haystack, B needle, string message = "Failed asserting array has key", string file = __FILE__, size_t line = __LINE__) if (isAssociativeArray!A);
379 |
380 |
Assert that an associative array contains a particular key.
381 |
382 |
Parameters A haystack
383 | The associative array to interogate.
384 | B needle
385 | The key the array should contain.
386 | string message
387 | The error message to display.
388 | string file
389 | The file name where the error occurred. The value is added automatically at the call site.
390 | size_t line
391 | The line where the error occurred. The value is added automatically at the call site.
392 |
393 |
Exceptions Thrown DUnitAssertError if the assertation fails.
394 |
Examples
395 | ["foo" :1, "bar" :2, "baz" :3, "qux" :4].assertHasKey ("foo" );
396 | [1:"foo" , 2:"bar" , 3:"baz" , 4:"qux" ].assertHasKey (1);
397 |
398 | ["foo" :"bar" ].assertHasKey ("baz" ).assertThrow!(DUnitAssertError)("Failed asserting array has key" );
400 |
401 |
402 |
403 |
void assertHasValue (A, B)(A haystack, B needle, string message = "Failed asserting array has value", string file = __FILE__, size_t line = __LINE__) if (isArray!A || isAssociativeArray!A);
404 |
405 |
Assert that an array contains a particular value.
406 |
407 |
Parameters A haystack
408 | The array to interogate.
409 | B needle
410 | The value the array should contain.
411 | string message
412 | The error message to display.
413 | string file
414 | The file name where the error occurred. The value is added automatically at the call site.
415 | size_t line
416 | The line where the error occurred. The value is added automatically at the call site.
417 |
418 |
Exceptions Thrown DUnitAssertError if the assertation fails.
419 |
Examples
420 | "Hello" .assertHasValue ("H" );
421 | [1, 2, 3, 4].assertHasValue (2);
422 | ["foo" , "bar" , "baz" , "qux" ].assertHasValue ("foo" );
423 | [["foo" , "bar" ], ["baz" , "qux" ]].assertHasValue (["foo" , "bar" ]);
424 | ["foo" :1, "bar" :2, "baz" :3, "qux" :4].assertHasValue (4);
425 |
426 | ["foo" :"bar" ].assertHasValue ("baz" ).assertThrow!(DUnitAssertError)("Failed asserting array has value" );
428 |
429 |
430 |
431 |
void assertInstanceOf (A, B)(B value, string message = "Failed asserting instance of", string file = __FILE__, size_t line = __LINE__);
432 |
433 |
Assert that a value is an instance of a type.
434 |
435 |
Parameters B value
436 | The value used during the assertion.
437 | string message
438 | The error message to display.
439 | string file
440 | The file name where the error occurred. The value is added automatically at the call site.
441 | size_t line
442 | The line where the error occurred. The value is added automatically at the call site.
443 |
444 |
Exceptions Thrown DUnitAssertError if the assertation fails.
445 |
Examples
446 | interface A {}
447 | class B : A {}
448 | class C : B {}
449 |
450 | auto b = new B();
451 | auto c = new C();
452 |
453 | b.assertInstanceOf !(Object)();
454 | b.assertInstanceOf !(A)();
455 | b.assertInstanceOf !(B)();
456 |
457 | c.assertInstanceOf !(Object)();
458 | c.assertInstanceOf !(A)();
459 | c.assertInstanceOf !(B)();
460 | c.assertInstanceOf !(C)();
461 |
462 | b.assertInstanceOf !(C)().assertThrow!(DUnitAssertError)("Failed asserting instance of" );
464 |
465 |
466 |
467 |
void assertLessThan (A, B)(A value, B threshold, string message = "Failed asserting less than", string file = __FILE__, size_t line = __LINE__);
468 |
469 |
Assert that a value is less than a threshold value.
470 |
471 |
Parameters A value
472 | The value used during the assertion.
473 | B threshold
474 | The threshold value.
475 | string message
476 | The error message to display.
477 | string file
478 | The file name where the error occurred. The value is added automatically at the call site.
479 | size_t line
480 | The line where the error occurred. The value is added automatically at the call site.
481 |
482 |
Exceptions Thrown DUnitAssertError if the assertation fails.
483 |
Examples
484 | 9.assertLessThan (10);
485 |
486 | 9.assertLessThan (8).assertThrow!(DUnitAssertError)("Failed asserting less than" );
488 |
489 |
490 |
491 |
void assertLessThanOrEqual (A, B)(A value, B threshold, string message = "Failed asserting less than or equal", string file = __FILE__, size_t line = __LINE__);
492 |
493 |
Assert that a value is less than or equal to a threshold value.
494 |
495 |
Parameters A value
496 | The value used during the assertion.
497 | B threshold
498 | The threshold value.
499 | string message
500 | The error message to display.
501 | string file
502 | The file name where the error occurred. The value is added automatically at the call site.
503 | size_t line
504 | The line where the error occurred. The value is added automatically at the call site.
505 |
506 |
Exceptions Thrown DUnitAssertError if the assertation fails.
507 |
Examples
508 | 10.assertLessThanOrEqual (10);
509 | 9.assertLessThanOrEqual (10);
510 |
511 | 9.assertLessThanOrEqual (8).assertThrow!(DUnitAssertError)("Failed asserting less than or equal" );
513 |
514 |
515 |
516 |
void assertMatchRegex (string value, string pattern, string message = "Failed asserting match to regex", string file = __FILE__, size_t line = __LINE__);
517 |
518 |
Assert that a string matches a regular expression.
519 |
520 |
Parameters string value
521 | The value used during the assertion.
522 | string pattern
523 | The regular expression pattern.
524 | string message
525 | The error message to display.
526 | string file
527 | The file name where the error occurred. The value is added automatically at the call site.
528 | size_t line
529 | The line where the error occurred. The value is added automatically at the call site.
530 |
531 |
Exceptions Thrown DUnitAssertError if the assertation fails.
532 |
Examples
533 | "foo" .assertMatchRegex (r"^foo$" );
534 | "192.168.0.1" .assertMatchRegex (r"((?:[\d]{1,3}\.){3}[\d]{1,3})" );
535 |
536 | "foo" .assertMatchRegex (r"^bar$" ).assertThrow!(DUnitAssertError)("Failed asserting match to regex" );
538 |
539 |
540 |
541 |
void assertNull (A)(A value, string message = "Failed asserting null", string file = __FILE__, size_t line = __LINE__) if (A.init is null);
542 |
543 |
Assert that a value is null .
544 |
545 |
Parameters A value
546 | The value to assert as null .
547 | string message
548 | The error message to display.
549 | string file
550 | The file name where the error occurred. The value is added automatically at the call site.
551 | size_t line
552 | The line where the error occurred. The value is added automatically at the call site.
553 |
554 |
Exceptions Thrown DUnitAssertError if the assertation fails.
555 |
Examples
556 | class T {}
557 |
558 | string foo;
559 | int [] bar;
560 | T t;
561 |
562 | foo.assertNull ();
563 | bar.assertNull ();
564 | t.assertNull ();
565 | null .assertNull ();
566 |
567 | "foo" .assertNull ().assertThrow!(DUnitAssertError)("Failed asserting null" );
569 |
570 |
571 |
572 |
void assertStartsWith (string value, string prefix, string message = "Failed asserting starts with", string file = __FILE__, size_t line = __LINE__);
573 |
574 |
Assert that a string starts with a particular string.
575 |
576 |
Parameters string value
577 | The value used during the assertion.
578 | string prefix
579 | The prefix to match.
580 | string message
581 | The error message to display.
582 | string file
583 | The file name where the error occurred. The value is added automatically at the call site.
584 | size_t line
585 | The line where the error occurred. The value is added automatically at the call site.
586 |
587 |
Exceptions Thrown DUnitAssertError if the assertation fails.
588 |
Examples
589 | "foo bar" .assertStartsWith ("foo" );
590 | "baz qux" .assertStartsWith ("baz" );
591 |
592 | "foo bar" .assertStartsWith ("baz" ).assertThrow!(DUnitAssertError)("Failed asserting starts with" );
594 |
595 |
596 |
597 |
void assertThrow (A : Throwable = Exception, B)(lazy B expression, string expressionMsg = null, string message = "Failed asserting throw", string file = __FILE__, size_t line = __LINE__);
598 |
599 |
Assert that an expression throws an exception.
600 |
601 |
Parameters B expression
602 | The expression to evaluate in order to assert the exception is thrown.
603 | string expressionMsg
604 | An optional expected message of the thrown exception.
605 | string message
606 | The error message to display.
607 | string file
608 | The file name where the error occurred. The value is added automatically at the call site.
609 | size_t line
610 | The line where the error occurred. The value is added automatically at the call site.
611 |
612 |
Exceptions Thrown DUnitAssertError if the assertation fails.
613 |
Examples
614 | import core.exception : AssertError, RangeError;
615 |
616 | class Foo : Exception
617 | {
618 | this (string message )
619 | {
620 | super (message );
621 | }
622 | }
623 |
624 | class Bar
625 | {
626 | public void baz()
627 | {
628 | throw new Foo("Thrown from baz." );
629 | }
630 | }
631 |
632 | auto bar = new Bar();
633 | bar.baz().assertThrow ();
634 | bar.baz().assertThrow !(Foo)("Thrown from baz." );
635 |
636 | delegate (){throw new Foo("Thrown from delegate." );}().assertThrow !(Exception)("Thrown from delegate." );
637 |
638 | auto baz = [0, 1, 2];
639 | baz[3].assertThrow !(RangeError)();
640 |
641 | assert (false ).assertThrow !(AssertError)("Assertion failure" );
642 |
643 | null .assertThrow ().assertThrow !(DUnitAssertError)("Failed asserting throw" );
645 |
646 | baz[3].assertThrow !(RangeError)("Foo" ).assertThrow !(DUnitAssertError)("Failed asserting throw" );
648 |
649 |
650 |
651 |
void assertTrue (T)(T value, string message = "Failed asserting true", string file = __FILE__, size_t line = __LINE__);
652 |
653 |
Assert that a boolean value is true .
654 |
655 |
Parameters T value
656 | The value used during the assertion.
657 | string message
658 | The error message to display.
659 | string file
660 | The file name where the error occurred. The value is added automatically at the call site.
661 | size_t line
662 | The line where the error occurred. The value is added automatically at the call site.
663 |
664 |
Exceptions Thrown DUnitAssertError if the assertation fails.
665 |
Examples
666 | true .assertTrue ();
667 |
668 | false .assertTrue ().assertThrow!(DUnitAssertError)("Failed asserting true" );
670 |
671 |
672 |
673 |
void assertTruthy (T)(T value, string message = "Failed asserting truthy", string file = __FILE__, size_t line = __LINE__);
674 |
675 |
Assert that a value evaluates as true .
676 |
677 |
Parameters T value
678 | The value used during the assertion.
679 | string message
680 | The error message to display.
681 | string file
682 | The file name where the error occurred. The value is added automatically at the call site.
683 | size_t line
684 | The line where the error occurred. The value is added automatically at the call site.
685 |
686 |
Exceptions Thrown DUnitAssertError if the assertation fails.
687 |
Examples
688 | true .assertTruthy ();
689 | ["foo" ].assertTruthy ();
690 | 1.assertTruthy ();
691 |
692 | false .assertTruthy ().assertThrow!(DUnitAssertError)("Failed asserting truthy" );
694 |
695 |
696 |
697 |
void assertType (A, B)(B value, string message = "Failed asserting type", string file = __FILE__, size_t line = __LINE__);
698 |
699 |
Assert that a value is of a particular type.
700 |
701 |
Parameters B value
702 | The value used during the assertion.
703 | string message
704 | The error message to display.
705 | string file
706 | The file name where the error occurred. The value is added automatically at the call site.
707 | size_t line
708 | The line where the error occurred. The value is added automatically at the call site.
709 |
710 |
Exceptions Thrown DUnitAssertError if the assertation fails.
711 |
Examples
712 | 1.assertType !(int )();
713 | "foo" .assertType !(string)();
714 | ["bar" ].assertType !(string[])();
715 | ['a'].assertType !(char [])();
716 |
717 | false .assertType !(string)().assertThrow!(DUnitAssertError)("Failed asserting type" );
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
--------------------------------------------------------------------------------
/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dunit",
3 | "description": "Advanced unit testing toolkit for the D programming language",
4 | "homepage": "https://github.com/nomad-software/dunit",
5 | "authors": ["Gary Willoughby"],
6 | "copyright": "Copyright (c) 2015 Gary Willoughby",
7 | "license": "MIT",
8 | "buildTypes": {
9 | "docs": {
10 | "buildOptions": ["deprecationErrors", "syntaxOnly", "warningsAsErrors"],
11 | "dflags": ["-c", "-Ddtemp", "-op"],
12 | "postBuildCommands": ["rm -rf docs", "mv temp/source docs", "rm -rf temp"]
13 | }
14 | },
15 | "configurations": [
16 | {
17 | "name": "library",
18 | "excludedSourceFiles": ["source/example.d"]
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/source/dunit/error.d:
--------------------------------------------------------------------------------
1 | /**
2 | * Module to handle exceptions.
3 | *
4 | * License:
5 | * MIT. See LICENSE for full details.
6 | */
7 | module dunit.error;
8 |
9 | /**
10 | * Imports.
11 | */
12 | import core.exception;
13 | import std.string;
14 |
15 |
16 | /**
17 | * An exception thrown when a unit test fails.
18 | *
19 | * This exception derives from AssertError to make it possible for
20 | * these errors to be thrown from nothrow methods.
21 | */
22 | class DUnitAssertError : AssertError
23 | {
24 | /**
25 | * Values to display in the message.
26 | */
27 | private string[] _log;
28 |
29 | /**
30 | * Constructor.
31 | *
32 | * Params:
33 | * message = The error message.
34 | * file = The file where the error occurred.
35 | * line = The line where the error occurred.
36 | */
37 | this(string message, string file, size_t line) pure nothrow @safe
38 | {
39 | super(message, file, line);
40 | }
41 |
42 | /**
43 | * Return the exception log.
44 | *
45 | * Returns:
46 | * The error's logged info, expectations and error messages.
47 | */
48 | public @property string[] log()
49 | {
50 | return this._log;
51 | }
52 |
53 | /**
54 | * Add a line of info to the exception log.
55 | *
56 | * Params:
57 | * caption = The caption.
58 | * value = The value.
59 | * icon = The icon before the caption.
60 | */
61 | public void addInfo(T)(string caption, T value, string icon = "ℹ")
62 | {
63 | this._log ~= format("%s %s: %s", icon, caption, value);
64 | }
65 |
66 | /**
67 | * Add a line of typed info to the exception log.
68 | *
69 | * Params:
70 | * caption = The caption.
71 | * value = The value.
72 | * icon = The icon before the caption.
73 | */
74 | public void addTypedInfo(T)(string caption, T value, string icon = "ℹ")
75 | {
76 | this._log ~= format("%s %s: (%s) %s", icon, caption, T.stringof, value);
77 | }
78 |
79 | /**
80 | * Add a line of expected info to the exception log.
81 | *
82 | * Params:
83 | * caption = The caption.
84 | * value = The value.
85 | * icon = The icon before the caption.
86 | */
87 | public void addExpectation(T)(string caption, T value, string icon = "✓")
88 | {
89 | this.addInfo!(T)(caption, value, icon);
90 | }
91 |
92 | /**
93 | * Add a line of typed expected info to the exception log.
94 | *
95 | * Params:
96 | * caption = The caption.
97 | * value = The value.
98 | * icon = The icon before the caption.
99 | */
100 | public void addTypedExpectation(T)(string caption, T value, string icon = "✓")
101 | {
102 | this.addTypedInfo!(T)(caption, value, icon);
103 | }
104 |
105 | /**
106 | * Add a line of error info to the exception log.
107 | *
108 | * Params:
109 | * caption = The caption.
110 | * value = The value.
111 | * icon = The icon before the caption.
112 | */
113 | public void addError(T)(string caption, T value, string icon = "✗")
114 | {
115 | this.addInfo!(T)(caption, value, icon);
116 | }
117 |
118 | /**
119 | * Add a line of typed error info to the exception log.
120 | *
121 | * Params:
122 | * caption = The caption.
123 | * value = The value.
124 | * icon = The icon before the caption.
125 | */
126 | public void addTypedError(T)(string caption, T value, string icon = "✗")
127 | {
128 | this.addTypedInfo!(T)(caption, value, icon);
129 | }
130 | }
131 |
132 | unittest
133 | {
134 | import dunit.toolkit;
135 |
136 | auto error = new DUnitAssertError("Error message.", "test.d", 100);
137 | error.addInfo("Info", 1);
138 | error.addTypedInfo("Typed info", 2);
139 | error.addExpectation("Expectation", 3);
140 | error.addTypedExpectation("Typed expectation", 4);
141 | error.addError("Error", 5);
142 | error.addTypedError("Typed error", 6);
143 |
144 | try
145 | {
146 | throw error;
147 | }
148 | catch (DUnitAssertError ex)
149 | {
150 | ex.msg.assertEqual("Error message.");
151 | ex.file.assertEqual("test.d");
152 | ex.line.assertEqual(100);
153 | ex.log.assertEqual(["ℹ Info: 1", "ℹ Typed info: (int) 2", "✓ Expectation: 3", "✓ Typed expectation: (int) 4", "✗ Error: 5", "✗ Typed error: (int) 6"]);
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/source/dunit/mockable.d:
--------------------------------------------------------------------------------
1 | /**
2 | * Mixin template to enable mocking.
3 | *
4 | * Many methods implement compile-time parameters (file, line) that are set at the call site.
5 | * It is preferred that these parameters are ignored when using these methods.
6 | *
7 | * License:
8 | * MIT. See LICENSE for full details.
9 | */
10 | module dunit.mockable;
11 |
12 | /**
13 | * Imports.
14 | */
15 | public import dunit.reflection;
16 |
17 | /**
18 | * A template mixin used to inject code into a class to provide mockable behaviour.
19 | * Code is nested within the host class to provide access to all host types.
20 | * $(B Only injects code when using the -unittest compiler switch.)
21 | *
22 | * Caveats:
23 | * Only module level types can be made mockable.
24 | *
25 | * Example:
26 | * ---
27 | * import dunit.mockable;
28 | *
29 | * class T
30 | * {
31 | * mixin Mockable!(T);
32 | * }
33 | * ---
34 | */
35 | public mixin template Mockable(C) if (is(C == class) || is(C == interface))
36 | {
37 | version(unittest):
38 |
39 | /*
40 | * Struct for holding method count information.
41 | */
42 | private struct MethodCount
43 | {
44 | /**
45 | * The expected minimum count of the method.
46 | */
47 | public ulong minimum;
48 |
49 | /**
50 | * The expected maximum count of the method.
51 | */
52 | public ulong maximum;
53 |
54 | /**
55 | * The actual count of the method.
56 | */
57 | public ulong actual;
58 | }
59 |
60 | /*
61 | * Struct for holding the location of the disableParentMethods method.
62 | */
63 | private struct FileLocation
64 | {
65 | /**
66 | * The name of the file.
67 | */
68 | public string file;
69 |
70 | /**
71 | * The line number in the file.
72 | */
73 | public size_t line;
74 | }
75 |
76 | /**
77 | * Injected by the Mockable mixin template this method allows creation of mock object instances of the mockable class.
78 | *
79 | * Params:
80 | * args = The constructor arguments of the host class.
81 | *
82 | * Example:
83 | * ---
84 | * import dunit.mockable;
85 | *
86 | * class T
87 | * {
88 | * mixin Mockable!(T);
89 | * }
90 | *
91 | * unittest
92 | * {
93 | * import dunit.toolkit;
94 | *
95 | * auto mock = T.getMock();
96 | *
97 | * assertTrue(cast(T)mock); // Mock extends T.
98 | * }
99 | * ---
100 | *
101 | * Templates:
102 | *
103 | * Templated classes are supported when creating mocks.
104 | * Simply include the template parameters for the class when calling the 'getMock' method.
105 | * ---
106 | * auto mock = T!(int).getMock(); // Get a mock of T!(int).
107 | * ---
108 | */
109 | static public auto getMock(A...)(A args)
110 | {
111 | return new Mock!(C)(args);
112 | }
113 |
114 | /**
115 | * Injected by the Mockable mixin template this class contains all mocking behaviour.
116 | *
117 | * This Mock class extends any class it's injected into and provides methods to interact with the mocked instance.
118 | * An instance of this class can dynamically replace any of its methods at runtime using the 'mockMethod' method.
119 | * All mocked methods can optionally have their call counts asserted to be within set limits.
120 | */
121 | private static class Mock(C) : C if (is(C == class) || is(C == interface))
122 | {
123 | import dunit.error;
124 | import dunit.moduleunittester;
125 | import std.range;
126 | import std.string;
127 | import std.traits;
128 |
129 | /*
130 | * The friendly class name.
131 | */
132 | private enum string className = C.stringof;
133 |
134 | /*
135 | * Records the call limits and call counts of each method.
136 | */
137 | private MethodCount[string] _methodCount;
138 |
139 | /*
140 | * Boolean representing whether to use the parent methods or not.
141 | */
142 | private bool _useParentMethods = true;
143 |
144 | /*
145 | * A structure to hold file location for the disableParentMethods method.
146 | */
147 | private FileLocation _disableMethodsLocation;
148 |
149 | /**
150 | * Inject the necessary class code.
151 | */
152 | static if (is(C == class))
153 | {
154 | mixin(DUnitConstructorIterator!(C, "Constructor!(T, func)"));
155 | }
156 | mixin(DUnitMethodIterator!(C, "MethodDelegateProperty!(func)"));
157 | mixin(DUnitMethodIterator!(C, "Method!(is(T == class), func)"));
158 |
159 | /*
160 | * Get the storage classes of the passed delegate.
161 | *
162 | * Params:
163 | * method = The delegate to inspect.
164 | *
165 | * Returns:
166 | * An array containing the storage classes of all delegate parameters.
167 | */
168 | private string[] getStorageClasses(T)(T method)
169 | {
170 | string[] storageClasses;
171 | string code;
172 |
173 | foreach (storageClass; ParameterStorageClassTuple!(method))
174 | {
175 | code = "";
176 |
177 | static if (storageClass == ParameterStorageClass.scope_)
178 | {
179 | code ~= "scope ";
180 | }
181 |
182 | static if (storageClass == ParameterStorageClass.lazy_)
183 | {
184 | code ~= "lazy ";
185 | }
186 |
187 | static if (storageClass == ParameterStorageClass.out_)
188 | {
189 | code ~= "out ";
190 | }
191 |
192 | static if (storageClass == ParameterStorageClass.ref_)
193 | {
194 | code ~= "ref ";
195 | }
196 |
197 | storageClasses ~= code;
198 | }
199 | return storageClasses;
200 | }
201 |
202 | /*
203 | * Get the types of the passed delegate.
204 | *
205 | * Params:
206 | * method = The delegate to inspect.
207 | *
208 | * Returns:
209 | * An array containing the types of all delegate parameters.
210 | */
211 | private string[] getTypes(T)(T method)
212 | {
213 | string[] types;
214 | foreach (type; ParameterTypeTuple!(method))
215 | {
216 | types ~= type.stringof;
217 | }
218 | return types;
219 | }
220 |
221 | /*
222 | * Generate a signature using the passed name and method. The signature is used to
223 | * match up delegates to methods behind the scenes.
224 | *
225 | * Params:
226 | * name = The name of the method to generate the signature for.
227 | * method = A delegate to inspect, retreiving parameter details.
228 | *
229 | * Returns:
230 | * A string containing a method signature.
231 | */
232 | private string generateSignature(T)(string name, T method)
233 | {
234 | string[] storageClasses = this.getStorageClasses!(T)(method);
235 | string[] types = this.getTypes!(T)(method);
236 | string[] parameters;
237 |
238 | foreach (storageClass, type; zip(storageClasses, types))
239 | {
240 | parameters ~= format("%s%s", storageClass, type);
241 | }
242 |
243 | return format("%s:%s(%s)", ReturnType!(method).stringof, name, parameters.join(", "));
244 | }
245 |
246 | /**
247 | * Replace a method in the mock object by adding a mock method to be called in its place.
248 | * $(B By default parent methods are called in lieu of any replacements unless parent methods are disabled.)
249 | *
250 | * Params:
251 | * name = The name of the method to replace. (Only the name should be used, no parameters, etc.)
252 | * delegate_ = The delegate to be used instead of calling the original method.
253 | * The delegate must have the exact signature of the method being replaced.
254 | * minimumCount = The minimum amount of times this method must be called when asserting calls.
255 | * maximumCount = The maximum amount of times this method must be called when asserting calls.
256 | * file = The file name where the error occurred. The value is added automatically at the call site.
257 | * line = The line where the error occurred. The value is added automatically at the call site.
258 | *
259 | * Throws:
260 | * DUnitAssertError if the passed delegate does not match the signature of any overload of the method named.
261 | *
262 | * Caveats:
263 | * $(OL
264 | * $(LI In the case of replacing overloaded methods the delegate signature defines which method to replace. Helpful errors will be raised on non matching signatures.)
265 | * $(LI Templated methods are final by default and therefore cannot be mocked.)
266 | * )
267 | *
268 | * See_Also:
269 | * $(LINK2 mockable.html#disableParentMethods, disableParentMethods(...))
270 | *
271 | * Example:
272 | * ---
273 | * import dunit.mockable;
274 | *
275 | * class T
276 | * {
277 | * int getValue()
278 | * {
279 | * return 1;
280 | * }
281 | *
282 | * mixin Mockable!(T);
283 | * }
284 | *
285 | * unittest
286 | * {
287 | * import dunit.toolkit;
288 | *
289 | * auto mock = T.getMock();
290 | *
291 | * // Replace the 'getValue' method.
292 | * mock.mockMethod("getValue", delegate(){
293 | * return 2;
294 | * });
295 | *
296 | * mock.getValue().assertEqual(2);
297 | * }
298 | * ---
299 | */
300 | public void mockMethod(T)(string name, T delegate_, ulong minimumCount = 0, ulong maximumCount = ulong.max, string file = __FILE__, size_t line = __LINE__)
301 | {
302 | string signature = this.generateSignature!(T)(name, delegate_);
303 |
304 | this._methodCount[signature] = MethodCount(minimumCount, maximumCount);
305 |
306 | switch(signature)
307 | {
308 | mixin(DUnitMethodIterator!(C, "MethodSignatureSwitch!(func)"));
309 | default:
310 | auto error = new DUnitAssertError("Delegate does not match method signature", file, line);
311 | error.addInfo("Method name", format("%s.%s", this.className, name));
312 | error.addError("Delegate signature", signature);
313 | throw error;
314 | }
315 | }
316 |
317 | /**
318 | * Disable parent methods being called if mock replacements are not implemented.
319 | * If parent methods have been disabled a helpful assert error will be raised on any attempt to call methods that haven't been replaced.
320 | * This is helpful if it's necessary to disable all behaviour of the mocked class.
321 | *
322 | * Has no effect on mock objects derived from interfaces, by default all mocked interface methods assert an error until replaced.
323 | *
324 | * Params:
325 | * file = The file name where the error occurred. The value is added automatically at the call site.
326 | * line = The line where the error occurred. The value is added automatically at the call site.
327 | *
328 | * See_Also:
329 | * $(LINK2 mockable.html#mockMethod, mockMethod(...))
330 | *
331 | * Example:
332 | * ---
333 | * import dunit.mockable;
334 | *
335 | * class T
336 | * {
337 | * mixin Mockable!(T);
338 | * }
339 | *
340 | * unittest
341 | * {
342 | * auto mock = T.getMock();
343 | * mock.disableParentMethods();
344 | *
345 | * // All mock object methods that are used in the test
346 | * // must now be replaced to avoid an error being thrown.
347 | * }
348 | * ---
349 | */
350 | public void disableParentMethods(string file = __FILE__, size_t line = __LINE__)
351 | {
352 | this._disableMethodsLocation.file = file;
353 | this._disableMethodsLocation.line = line;
354 | this._useParentMethods = false;
355 | }
356 |
357 | /**
358 | * Assert all replaced methods are called the defined amount of times.
359 | *
360 | * Params:
361 | * message = The error message to display.
362 | * file = The file name where the error occurred. The value is added automatically at the call site.
363 | * line = The line where the error occurred. The value is added automatically at the call site.
364 | *
365 | * Throws:
366 | * DUnitAssertError if any method was called outside of preset boundries.
367 | *
368 | * Example:
369 | * ---
370 | * import dunit.mockable;
371 | *
372 | * class T
373 | * {
374 | * int getValue()
375 | * {
376 | * return 1;
377 | * }
378 | *
379 | * mixin Mockable!(T);
380 | * }
381 | *
382 | * unittest
383 | * {
384 | * import dunit.toolkit;
385 | *
386 | * auto mock = T.getMock();
387 | *
388 | * // Replace method while defining a minimum call limit.
389 | * mock.mockMethod("getValue", delegate(){
390 | * return 2;
391 | * }, 1);
392 | *
393 | * // Increase the call count of 'getValue' by one.
394 | * mock.getValue().assertEqual(2);
395 | *
396 | * // Assert methods calls are within defined limits.
397 | * mock.assertMethodCalls();
398 | * }
399 | * ---
400 | */
401 | public void assertMethodCalls(string message = "Failed asserting call count", string file = __FILE__, size_t line = __LINE__)
402 | {
403 | foreach (signature, methodCount; this._methodCount)
404 | {
405 | if (methodCount.actual < methodCount.minimum)
406 | {
407 | auto error = new DUnitAssertError(message, file, line);
408 |
409 | error.addInfo("Method", format("%s.%s", this.className, signature));
410 | error.addExpectation("Minimum allowed calls", methodCount.minimum);
411 | error.addError("Actual calls", methodCount.actual);
412 |
413 | throw error;
414 | }
415 |
416 | if (methodCount.actual > methodCount.maximum)
417 | {
418 | auto error = new DUnitAssertError(message, file, line);
419 |
420 | error.addInfo("Method", format("%s.%s", this.className, signature));
421 | error.addExpectation("Maximum allowed calls", methodCount.maximum);
422 | error.addError("Actual calls", methodCount.actual);
423 |
424 | throw error;
425 | }
426 | }
427 | }
428 | }
429 | }
430 |
431 | unittest
432 | {
433 | import dunit.toolkit;
434 |
435 | interface T
436 | {
437 | public int method1(int param) const;
438 | public void method2(int param) const;
439 | public int method3(int param) const pure @safe nothrow;
440 | public void method4(int param) const pure @safe nothrow;
441 | public int method5(int param) const pure @trusted nothrow;
442 | public void method6(int param) const pure @trusted nothrow;
443 |
444 | mixin Mockable!T;
445 | }
446 |
447 | auto mock = T.getMock();
448 |
449 | mock.mockMethod("method1", delegate(int param) { return 2*param; });
450 | mock.mockMethod("method2", delegate(int param) { });
451 | mock.mockMethod("method3", delegate(int param) { return 2*param; });
452 | mock.mockMethod("method4", delegate(int param) { });
453 | mock.mockMethod("method5", delegate(int param) { return 2*param; });
454 | mock.mockMethod("method6", delegate(int param) { });
455 |
456 | mock.method1(10).assertEqual(20);
457 | mock.method2(10);
458 | mock.method3(10).assertEqual(20);
459 | mock.method4(10);
460 | mock.method5(10).assertEqual(20);
461 | mock.method6(10);
462 | }
463 |
464 | unittest
465 | {
466 | import dunit.toolkit;
467 |
468 | static class T
469 | {
470 | public int method1(int param) const { return 0; };
471 | public void method2(int param) const {};
472 | public int method3(int param) const pure @safe nothrow { return 0; };
473 | public void method4(int param) const pure @safe nothrow {};
474 | public int method5(int param) const pure @trusted nothrow { return 0; };
475 | public void method6(int param) const pure @trusted nothrow {};
476 |
477 | mixin Mockable!T;
478 | }
479 |
480 | auto mock = T.getMock();
481 |
482 | mock.mockMethod("method1", delegate(int param) { return 2*param; });
483 | mock.mockMethod("method2", delegate(int param) { });
484 | mock.mockMethod("method3", delegate(int param) { return 2*param; });
485 | mock.mockMethod("method4", delegate(int param) { });
486 | mock.mockMethod("method5", delegate(int param) { return 2*param; });
487 | mock.mockMethod("method6", delegate(int param) { });
488 |
489 | mock.method1(10).assertEqual(20);
490 | mock.method2(10);
491 | mock.method3(10).assertEqual(20);
492 | mock.method4(10);
493 | mock.method5(10).assertEqual(20);
494 | mock.method6(10);
495 | }
496 |
497 | unittest
498 | {
499 | import dunit.toolkit;
500 |
501 | static class T
502 | {
503 | public int method1() nothrow
504 | {
505 | assert(false, "thrown from method1");
506 | }
507 |
508 | public void method2()
509 | {
510 | throw new Exception("thrown from method2");
511 | }
512 |
513 | mixin Mockable!(T);
514 | }
515 |
516 | auto mock = T.getMock();
517 |
518 | mock.method1().assertThrow!Throwable("thrown from method1");
519 | mock.method2().assertThrow!Exception("thrown from method2");
520 | }
521 |
--------------------------------------------------------------------------------
/source/dunit/moduleunittester.d:
--------------------------------------------------------------------------------
1 | /**
2 | * Module to replace the built-in unit tester.
3 | *
4 | * License:
5 | * MIT. See LICENSE for full details.
6 | */
7 | module dunit.moduleunittester;
8 |
9 | /**
10 | * Imports.
11 | */
12 | import core.exception;
13 | import core.runtime;
14 | import dunit.error;
15 | import dunit.output.console;
16 | import dunit.result.moduleresult;
17 | import dunit.result.moduleresultcollection;
18 |
19 | /**
20 | * Replace the standard unit test handler.
21 | */
22 | version(unittest) shared static this()
23 | {
24 | Runtime.moduleUnitTester = function()
25 | {
26 | auto console = new Console();
27 | auto results = new ModuleResultCollection();
28 |
29 | console.writeHeader();
30 |
31 | foreach (module_; ModuleInfo)
32 | {
33 | if (module_)
34 | {
35 | auto unitTest = module_.unitTest;
36 |
37 | if (unitTest)
38 | {
39 | try
40 | {
41 | unitTest();
42 | }
43 | catch (DUnitAssertError ex)
44 | {
45 | results.add(new ModuleResult(module_.name, ex));
46 | continue;
47 | }
48 | catch (AssertError ex)
49 | {
50 | results.add(new ModuleResult(module_.name, new DUnitAssertError(ex.msg, ex.file, ex.line)));
51 | continue;
52 | }
53 | results.add(new ModuleResult(module_.name));
54 | }
55 | }
56 | }
57 |
58 | console.writeReport(results);
59 |
60 | return !results.failedCount();
61 | };
62 | }
63 |
--------------------------------------------------------------------------------
/source/dunit/output/console.d:
--------------------------------------------------------------------------------
1 | /**
2 | * Module to handle output to the console.
3 | *
4 | * License:
5 | * MIT. See LICENSE for full details.
6 | */
7 | module dunit.output.console;
8 |
9 | /**
10 | * Imports.
11 | */
12 | import dunit.error;
13 | import dunit.result.moduleresultcollection;
14 | import std.array;
15 | import std.conv;
16 | import std.range;
17 | import std.stdio;
18 | import std.string;
19 |
20 | /**
21 | * Format output to the console.
22 | */
23 | class Console
24 | {
25 | /**
26 | * Write a line to the console.
27 | *
28 | * params:
29 | * line = The line to write.
30 | */
31 | public void write(string line)
32 | {
33 | writefln("%s", line);
34 | }
35 |
36 | /**
37 | * Write an indented line to the console.
38 | *
39 | * params:
40 | * line = The line to write.
41 | * indent = The space indent before the line.
42 | */
43 | public void write(string line, int indent)
44 | {
45 | this.write(format("%s%s", " ".repeat(indent).join(), line));
46 | }
47 |
48 | /**
49 | * Write a prefixed line to the console.
50 | *
51 | * params:
52 | * prefix = The prefix of the line.
53 | * line = The line to write.
54 | */
55 | public void write(string prefix, string line)
56 | {
57 | this.write(format("%s %s", prefix, line));
58 | }
59 |
60 | /**
61 | * Write an intented, prefixed line to the console.
62 | *
63 | * params:
64 | * prefix = The prefix of the line.
65 | * line = The line to write.
66 | * indent = The space indent before the line.
67 | */
68 | public void write(string prefix, string line, int indent)
69 | {
70 | this.write(format("%s%s %s", " ".repeat(indent).join(), prefix, line));
71 | }
72 |
73 | /**
74 | * Write the header.
75 | */
76 | public void writeHeader()
77 | {
78 | this.write("");
79 | this.write(">", "Running unit tests");
80 | }
81 |
82 | /**
83 | * Format and write an error to the console.
84 | *
85 | * Params:
86 | * ex = The exception to output.
87 | */
88 | private void writeError(DUnitAssertError ex)
89 | {
90 | this.write("");
91 | this.write("+----------------------------------------------------------------------", 2);
92 | this.write("|", ex.msg, 2);
93 | this.write("+----------------------------------------------------------------------", 2);
94 | this.write("| File:", ex.file, 2);
95 | this.write("| Line:", ex.line.text(), 2);
96 | this.write("+----------------------------------------------------------------------", 2);
97 | foreach (info; ex.log)
98 | {
99 | this.write("|", info, 2);
100 | }
101 | this.write("");
102 | }
103 |
104 | /**
105 | * Output a detailed report.
106 | *
107 | * Params:
108 | * results = A module result collection.
109 | */
110 | public void writeReport(ModuleResultCollection results)
111 | {
112 | foreach (result; results)
113 | {
114 | this.write("-", result.source);
115 |
116 | if (result.error)
117 | {
118 | this.writeError(result.error);
119 | }
120 | }
121 |
122 | if (results.failedCount())
123 | {
124 | this.write(">", format("%s tests run. %s passed, %s failed",
125 | results.totalCount(),
126 | results.passedCount(),
127 | results.failedCount()
128 | ));
129 | }
130 | else
131 | {
132 | this.write(">", format("All %s tests passed", results.totalCount()));
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/source/dunit/reflection.d:
--------------------------------------------------------------------------------
1 | /**
2 | * Internal reflection templates for implementing mocking behaviour.
3 | *
4 | * License:
5 | * MIT. See LICENSE for full details.
6 | */
7 | module dunit.reflection;
8 |
9 | /**
10 | * Imports.
11 | */
12 | import dunit.toolkit;
13 | import std.array;
14 | import std.range;
15 | import std.string;
16 | import std.traits;
17 |
18 | /**
19 | * Generate a string containing the protection level of the passed function.
20 | *
21 | * Params:
22 | * func = The function to inspect.
23 | */
24 | private template MethodProtection(func...) if (func.length == 1 && isCallable!(func))
25 | {
26 | enum MethodProtection = __traits(getProtection, func);
27 | }
28 |
29 | unittest
30 | {
31 | class T
32 | {
33 | public void method1(){}
34 | protected void method2(){}
35 | private void method3(){}
36 | }
37 |
38 | MethodProtection!(T.method1).assertEqual("public");
39 | MethodProtection!(T.method2).assertEqual("protected");
40 | MethodProtection!(T.method3).assertEqual("private");
41 | }
42 |
43 | /**
44 | * Generate a string containing the synchronization of the passed function.
45 | *
46 | * Params:
47 | * func = The function to inspect.
48 | */
49 | private template MethodSynchonization(func...) if (func.length == 1 && isCallable!(func))
50 | {
51 | enum MethodSynchonization = isMethodShared!(func) ? "shared" : "";
52 | }
53 |
54 | unittest
55 | {
56 | class T
57 | {
58 | public void method1() shared {}
59 | public synchronized void method2(){}
60 | public void method3(){}
61 | }
62 |
63 | MethodSynchonization!(T.method1).assertEqual("shared");
64 | MethodSynchonization!(T.method2).assertEqual("shared");
65 | MethodSynchonization!(T.method3).assertEqual("");
66 | }
67 |
68 | /**
69 | * Generate a string containing the attributes of the passed function.
70 | *
71 | * Params:
72 | * func = The function to inspect.
73 | */
74 | private template MethodAttributes(func...) if (func.length == 1 && isCallable!(func))
75 | {
76 | private string getMethodAttributes()
77 | {
78 | string code = "";
79 |
80 | with (FunctionAttribute)
81 | {
82 | static if (functionAttributes!(func) & property)
83 | {
84 | code ~= "@property ";
85 | }
86 |
87 | static if (functionAttributes!(func) & trusted)
88 | {
89 | code ~= "@trusted ";
90 | }
91 |
92 | static if (functionAttributes!(func) & safe)
93 | {
94 | code ~= "@safe ";
95 | }
96 |
97 | static if (functionAttributes!(func) & pure_)
98 | {
99 | code ~= "pure ";
100 | }
101 |
102 | static if (functionAttributes!(func) & nothrow_)
103 | {
104 | code ~= "nothrow ";
105 | }
106 |
107 | static if (functionAttributes!(func) & ref_)
108 | {
109 | code ~= "ref ";
110 | }
111 | }
112 | return code.stripRight();
113 | }
114 | enum MethodAttributes = getMethodAttributes();
115 | }
116 |
117 | unittest
118 | {
119 | class T
120 | {
121 | public @property @trusted void method1(){}
122 | public @safe pure nothrow void method2(){}
123 | public ref int method3(ref int foo){return foo;}
124 | }
125 |
126 | MethodAttributes!(T.method1).assertEqual("@property @trusted");
127 | MethodAttributes!(T.method2).assertEqual("@safe pure nothrow");
128 | MethodAttributes!(T.method3).assertEqual("ref");
129 | }
130 |
131 | /**
132 | * Generate a string containing the return type of the passed function.
133 | *
134 | * Params:
135 | * func = The function to inspect.
136 | */
137 | private template MethodReturnType(func...) if (func.length == 1 && isCallable!(func))
138 | {
139 | enum MethodReturnType = ReturnType!(func).stringof;
140 | }
141 |
142 | unittest
143 | {
144 | class T
145 | {
146 | public int method1(){return 1;}
147 | public string method2(){return "foo";}
148 | public char method3(){return 'a';}
149 | }
150 |
151 | MethodReturnType!(T.method1).assertEqual("int");
152 | MethodReturnType!(T.method2).assertEqual("string");
153 | MethodReturnType!(T.method3).assertEqual("char");
154 | }
155 |
156 | /**
157 | * Generate a string containing the name of the passed function.
158 | *
159 | * Params:
160 | * func = The function to inspect.
161 | */
162 | private template MethodName(func...) if (func.length == 1 && isCallable!(func))
163 | {
164 | enum MethodName = __traits(identifier, func);
165 | }
166 |
167 | unittest
168 | {
169 | class T
170 | {
171 | public void method1(){}
172 | public void method2(){}
173 | public void method3(){}
174 | }
175 |
176 | MethodName!(T.method1).assertEqual("method1");
177 | MethodName!(T.method2).assertEqual("method2");
178 | MethodName!(T.method3).assertEqual("method3");
179 | }
180 |
181 | /**
182 | * Generate a string array containing the storage classes of each parameter (if any) of the passed function.
183 | *
184 | * Params:
185 | * func = The function to inspect.
186 | */
187 | private template MethodParameterStorageClasses(func...) if (func.length == 1 && isCallable!(func))
188 | {
189 | private string[] getMethodParameterStorageClasses()
190 | {
191 | string[] storageClasses;
192 | string code;
193 |
194 | foreach (storageClass; ParameterStorageClassTuple!(func))
195 | {
196 | code = "";
197 |
198 | static if (storageClass == ParameterStorageClass.scope_)
199 | {
200 | code ~= "scope ";
201 | }
202 |
203 | static if (storageClass == ParameterStorageClass.lazy_)
204 | {
205 | code ~= "lazy ";
206 | }
207 |
208 | static if (storageClass == ParameterStorageClass.out_)
209 | {
210 | code ~= "out ";
211 | }
212 |
213 | static if (storageClass == ParameterStorageClass.ref_)
214 | {
215 | code ~= "ref ";
216 | }
217 |
218 | storageClasses ~= code;
219 | }
220 |
221 | return storageClasses;
222 | }
223 | enum MethodParameterStorageClasses = getMethodParameterStorageClasses();
224 | }
225 |
226 | unittest
227 | {
228 | class T
229 | {
230 | public void method1(scope int* foo){}
231 | public void method2(lazy int bar){}
232 | public void method3(out int baz){}
233 | public void method4(ref int qux){}
234 | }
235 |
236 | MethodParameterStorageClasses!(T.method1).assertEqual(["scope "]);
237 | MethodParameterStorageClasses!(T.method2).assertEqual(["lazy "]);
238 | MethodParameterStorageClasses!(T.method3).assertEqual(["out "]);
239 | MethodParameterStorageClasses!(T.method4).assertEqual(["ref "]);
240 | }
241 |
242 | /**
243 | * Generate a string array containing the parameter types of the passed function.
244 | *
245 | * Params:
246 | * func = The function to inspect.
247 | */
248 | private template MethodParameterTypes(func...) if (func.length == 1 && isCallable!(func))
249 | {
250 | private string[] getMethodParameterTypes()
251 | {
252 | string[] types;
253 |
254 | foreach (type; ParameterTypeTuple!(func))
255 | {
256 | types ~= type.stringof;
257 | }
258 |
259 | return types;
260 | }
261 | enum MethodParameterTypes = getMethodParameterTypes();
262 | }
263 |
264 | unittest
265 | {
266 | class T
267 | {
268 | public void method1(const int foo){}
269 | public void method2(string bar){}
270 | public void method3(bool baz){}
271 | }
272 |
273 | MethodParameterTypes!(T.method1).assertEqual(["const(int)"]);
274 | MethodParameterTypes!(T.method2).assertEqual(["string"]);
275 | MethodParameterTypes!(T.method3).assertEqual(["bool"]);
276 | }
277 |
278 | /**
279 | * Generate a string array containing the parameter identifiers of the passed function.
280 | *
281 | * Params:
282 | * func = The function to inspect.
283 | */
284 | private template MethodParameterIdentifiers(func...) if (func.length == 1 && isCallable!(func))
285 | {
286 | private string[] getMethodParameterIdentifiers()
287 | {
288 | string[] names;
289 |
290 | foreach (name; ParameterIdentifierTuple!(func))
291 | {
292 | names ~= name;
293 | }
294 |
295 | return names;
296 | }
297 | enum MethodParameterIdentifiers = getMethodParameterIdentifiers();
298 | }
299 |
300 | unittest
301 | {
302 | class T
303 | {
304 | public void method1(const int foo){}
305 | public void method2(string bar){}
306 | public void method3(bool baz){}
307 | }
308 |
309 | MethodParameterIdentifiers!(T.method1).assertEqual(["foo"]);
310 | MethodParameterIdentifiers!(T.method2).assertEqual(["bar"]);
311 | MethodParameterIdentifiers!(T.method3).assertEqual(["baz"]);
312 | }
313 |
314 | /**
315 | * Generate a string array containing the default values of each parameter (if any) of the passed function.
316 | *
317 | * Params:
318 | * func = The function to inspect.
319 | */
320 | private template MethodParameterDefaultValues(func...) if (func.length == 1 && isCallable!(func))
321 | {
322 | private string[] getMethodParameterDefaultValues()
323 | {
324 | string[] defaultValues;
325 |
326 | foreach (defaultValue; ParameterDefaultValueTuple!(func))
327 | {
328 | static if (is(defaultValue == void))
329 | {
330 | defaultValues ~= "";
331 | }
332 | else
333 | {
334 | defaultValues ~= " = " ~ defaultValue.stringof;
335 | }
336 | }
337 |
338 | return defaultValues;
339 | }
340 | enum MethodParameterDefaultValues = getMethodParameterDefaultValues();
341 | }
342 |
343 | unittest
344 | {
345 | class T
346 | {
347 | public void method1(const int foo){}
348 | public void method2(string bar = "qux"){}
349 | public void method3(bool baz = true){}
350 | }
351 |
352 | MethodParameterDefaultValues!(T.method1).assertEqual([""]);
353 | MethodParameterDefaultValues!(T.method2).assertEqual([" = \"qux\""]);
354 | MethodParameterDefaultValues!(T.method3).assertEqual([" = true"]);
355 | }
356 |
357 | /**
358 | * Generate a string containing the full parameter signature of the passed function.
359 | *
360 | * Params:
361 | * func = The function to inspect.
362 | */
363 | private template MethodParameters(func...) if (func.length == 1 && isCallable!(func))
364 | {
365 | private string getMethodParameters()
366 | {
367 | string[] storageClasses = MethodParameterStorageClasses!(func);
368 | string[] types = MethodParameterTypes!(func);
369 | string[] names = MethodParameterIdentifiers!(func);
370 | string[] defaultValues = MethodParameterDefaultValues!(func);
371 |
372 | string[] parameters;
373 |
374 | foreach (storageClass, type, name, defaultValue; zip(storageClasses, types, names, defaultValues))
375 | {
376 | parameters ~= format("%s%s %s%s", storageClass, type, name, defaultValue);
377 | }
378 |
379 | return format("(%s)", parameters.join(", "));
380 | }
381 | enum MethodParameters = getMethodParameters();
382 | }
383 |
384 | unittest
385 | {
386 | class T
387 | {
388 | public void method1(const int foo){}
389 | public void method2(string bar = "qux"){}
390 | public void method3(bool baz = true){}
391 | }
392 |
393 | MethodParameters!(T.method1).assertEqual("(const(int) foo)");
394 | MethodParameters!(T.method2).assertEqual("(string bar = \"qux\")");
395 | MethodParameters!(T.method3).assertEqual("(bool baz = true)");
396 | }
397 |
398 | /**
399 | * Generate a string containing the mangled name of the passed function.
400 | *
401 | * Params:
402 | * func = The function to inspect.
403 | */
404 | private template MethodMangledName(func...) if (func.length == 1 && isCallable!(func))
405 | {
406 | enum MethodMangledName = mangledName!(func);
407 | }
408 |
409 | /**
410 | * Returns true if the passed function is nothrow, false if not.
411 | *
412 | * Params:
413 | * func = The function to inspect.
414 | */
415 | private template isMethodNoThrow(func...) if (func.length == 1 && isCallable!(func))
416 | {
417 | enum isMethodNoThrow = cast(bool)(functionAttributes!(func) & FunctionAttribute.nothrow_);
418 | }
419 |
420 | unittest
421 | {
422 | class T
423 | {
424 | public void method1() nothrow {}
425 | public void method2() {}
426 | }
427 |
428 | isMethodNoThrow!(T.method1).assertTrue();
429 | isMethodNoThrow!(T.method2).assertFalse();
430 | }
431 |
432 | /**
433 | * Returns true if the passed function is const, false if not.
434 | *
435 | * Params:
436 | * func = The function to inspect.
437 | */
438 | private template isMethodConst(func...) if (func.length == 1 && isCallable!(func))
439 | {
440 | enum isMethodConst = !isMutable!(FunctionTypeOf!(func));
441 | }
442 |
443 | unittest
444 | {
445 | class T
446 | {
447 | public void method1() const {}
448 | public void method2() immutable {}
449 | public void method3(){}
450 | }
451 |
452 | isMethodConst!(T.method1).assertTrue();
453 | isMethodConst!(T.method2).assertTrue();
454 | isMethodConst!(T.method3).assertFalse();
455 | }
456 |
457 | /**
458 | * Returns true if the passed function is shared or synchronized, false if not.
459 | *
460 | * Params:
461 | * func = The function to inspect.
462 | */
463 | private template isMethodShared(func...) if (func.length == 1 && isCallable!(func))
464 | {
465 | enum isMethodShared = is(shared(FunctionTypeOf!(func)) == FunctionTypeOf!(func));
466 | }
467 |
468 | unittest
469 | {
470 | static class T
471 | {
472 | public synchronized void method1() {}
473 | public void method2(int value) shared {}
474 | public void method3(int value) shared const pure nothrow @safe @property {}
475 | public shared(T) method4() shared { return null; }
476 | public shared(T) method5() { return null; }
477 | public void method6() {}
478 | }
479 |
480 | isMethodShared!(T.method1).assertTrue();
481 | isMethodShared!(T.method2).assertTrue();
482 | isMethodShared!(T.method3).assertTrue();
483 | isMethodShared!(T.method4).assertTrue();
484 | isMethodShared!(T.method5).assertFalse();
485 | isMethodShared!(T.method6).assertFalse();
486 | }
487 |
488 | /**
489 | * Generate a string containing the body of the passed function.
490 | *
491 | * Params:
492 | * hasParent = true if this function is replacing a parent implementation.
493 | * func = The function to inspect and generate code for.
494 | */
495 | private template MethodBody(bool hasParent, func...)
496 | {
497 | private string getMethodBody()
498 | {
499 | string code = "";
500 | code ~= "\ttry\n";
501 | code ~= "\t{\n";
502 |
503 | static if (!isMethodConst!(func))
504 | {
505 | code ~= "\t\tif (\"" ~ MethodSignature!(func) ~ "\" in this._methodCount)\n";
506 | code ~= "\t\t{\n";
507 | code ~= "\t\t\tthis._methodCount[\"" ~ MethodSignature!(func) ~ "\"].actual++;\n";
508 | code ~= "\t\t}\n";
509 | }
510 |
511 | code ~= "\t\tif (this." ~ MethodMangledName!(func) ~ ")\n";
512 | code ~= "\t\t{\n";
513 | code ~= "\t\t\treturn this." ~ MethodMangledName!(func) ~ "(" ~ MethodParameterIdentifiers!(func).join(", ") ~ ");\n";
514 | code ~= "\t\t}\n";
515 | code ~= "\t\telse\n";
516 | code ~= "\t\t{\n";
517 |
518 | static if (hasParent)
519 | {
520 | code ~= "\t\t\tif (this._useParentMethods)\n";
521 | code ~= "\t\t\t{\n";
522 | code ~= "\t\t\t\treturn super." ~ MethodName!(func) ~ "(" ~ MethodParameterIdentifiers!(func).join(", ") ~ ");\n";
523 | code ~= "\t\t\t}\n";
524 | code ~= "\t\t\telse\n";
525 | code ~= "\t\t\t{\n";
526 | code ~= "\t\t\t\tauto error = new DUnitAssertError(\"Mock method not implemented\", this._disableMethodsLocation.file, this._disableMethodsLocation.line);\n";
527 | code ~= "\t\t\t\terror.addInfo(\"Method\", this.className ~ \"." ~ MethodSignature!(func) ~ "\");\n";
528 | code ~= "\t\t\t\tthrow error;\n";
529 | code ~= "\t\t\t}\n";
530 | }
531 | else
532 | {
533 | code ~= "\t\t\t\tauto error = new DUnitAssertError(\"Mock method not implemented\", this._disableMethodsLocation.file, this._disableMethodsLocation.line);\n";
534 | code ~= "\t\t\t\terror.addInfo(\"Method\", this.className ~ \"." ~ MethodSignature!(func) ~ "\");\n";
535 | code ~= "\t\t\t\tthrow error;\n";
536 | }
537 |
538 | code ~= "\t\t}\n";
539 | code ~= "\t}\n";
540 | code ~= "\tcatch(Exception ex)\n";
541 | code ~= "\t{\n";
542 |
543 | static if (isMethodNoThrow!(func))
544 | {
545 | code ~= "\t\tassert(false, ex.msg);\n";
546 | }
547 | else
548 | {
549 | code ~= "\t\tthrow ex;\n";
550 | }
551 |
552 | code ~= "\t}\n";
553 | code ~= "\tassert(false, \"Critical error occurred!\");\n";
554 | return code;
555 | }
556 | enum MethodBody = getMethodBody();
557 | }
558 |
559 | /**
560 | * Generate a string containing the code for a delegate property.
561 | *
562 | * Params:
563 | * func = The function to inspect.
564 | */
565 | private template MethodDelegateProperty(func...) if (func.length == 1 && isCallable!(func))
566 | {
567 | private string getMethodDelegateProperty()
568 | {
569 | return format("private %s %s;\n", MethodDelegateSignature!(func), MethodMangledName!(func));
570 | }
571 | enum MethodDelegateProperty = getMethodDelegateProperty();
572 | }
573 |
574 | /**
575 | * Generate a string containing the entire code for the passed method.
576 | *
577 | * Params:
578 | * hasParent = true if this function is replacing a parent implementation.
579 | * func = The function to inspect and generate code for.
580 | */
581 | private template Method(bool hasParent, func...) if (func.length == 1 && isCallable!(func))
582 | {
583 | private string getMethod()
584 | {
585 | string code = "";
586 |
587 | static if (hasParent)
588 | {
589 | code ~= "override ";
590 | }
591 |
592 | code ~= MethodProtection!(func) ~ " ";
593 | code ~= MethodAttributes!(func) ~ " ";
594 | code ~= MethodReturnType!(func) ~ " ";
595 | code ~= MethodName!(func);
596 | code ~= MethodParameters!(func) ~ (isMethodConst!(func) ? " const \n" : "\n");
597 | code ~= "{\n";
598 | code ~= MethodBody!(hasParent, func);
599 | code ~= "}\n";
600 | return code;
601 | }
602 | enum Method = getMethod();
603 | }
604 |
605 | /**
606 | * Iterate through the methods of T generating code using the generator.
607 | *
608 | * Params:
609 | * T = The class to inspect.
610 | * generator = The template to use to generate code for each method.
611 | * index = The beginning index of the members.
612 | */
613 | public template DUnitMethodIterator(T, string generator, int index = 0) if (is(T == class) || is(T == interface))
614 | {
615 | private string getResult()
616 | {
617 | string code = "";
618 | static if (index < __traits(allMembers, T).length)
619 | {
620 | static if (MemberFunctionsTuple!(T, __traits(allMembers, T)[index]).length)
621 | {
622 | foreach (func; __traits(getVirtualMethods, T, __traits(allMembers, T)[index]))
623 | {
624 | static if (!__traits(isFinalFunction, func))
625 | {
626 | mixin("code ~= " ~ generator ~ ";");
627 | }
628 | }
629 | }
630 | code ~= DUnitMethodIterator!(T, generator, index + 1);
631 | }
632 | return code;
633 | }
634 | enum DUnitMethodIterator = getResult();
635 | }
636 |
637 | /**
638 | * Generate a string containing the entire override code for the passed constructor.
639 | *
640 | * Params:
641 | * T = The class to inspect.
642 | * func = The function to inspect.
643 | */
644 | private template Constructor(T, func...) if (is(T == class) && func.length == 1 && isCallable!(func))
645 | {
646 | private string getConstructor()
647 | {
648 | string code = "";
649 | code ~= "this";
650 | code ~= MethodParameters!(func) ~ "\n";
651 | code ~= "{\n";
652 | code ~= "\tsuper(" ~ MethodParameterIdentifiers!(func).join(", ") ~ ");\n";
653 | code ~= "}\n";
654 | return code;
655 | }
656 | enum Constructor = getConstructor();
657 | }
658 |
659 | unittest
660 | {
661 | class T
662 | {
663 | this(int foo, int bar)
664 | {
665 | }
666 | }
667 |
668 | string code = "this(int foo, int bar)
669 | {
670 | super(foo, bar);
671 | }\n";
672 |
673 | Constructor!(T, T.__ctor).assertEqual(code);
674 | }
675 |
676 | /**
677 | * Iterate through the constructors of T generating code using the generator.
678 | *
679 | * Params:
680 | * T = The class to inspect.
681 | * generator = The template to use to generate code for each constructor.
682 | */
683 | public template DUnitConstructorIterator(T, string generator) if (is(T == class))
684 | {
685 | private string getResult()
686 | {
687 | string code = "";
688 | static if (__traits(hasMember, T, "__ctor"))
689 | {
690 | foreach (func; __traits(getOverloads, T, "__ctor"))
691 | {
692 | mixin("code ~= " ~ generator ~ ";");
693 | }
694 | }
695 | return code;
696 | }
697 | enum DUnitConstructorIterator = getResult();
698 | }
699 |
700 | unittest
701 | {
702 | class A
703 | {
704 | this(){}
705 | this(int foo, int bar)
706 | {
707 | }
708 | }
709 |
710 | class B {}
711 |
712 | string code = "this()
713 | {
714 | super();
715 | }
716 | this(int foo, int bar)
717 | {
718 | super(foo, bar);
719 | }\n";
720 |
721 | DUnitConstructorIterator!(A, "Constructor!(T, func)").assertEqual(code);
722 | DUnitConstructorIterator!(B, "Constructor!(T, func)").assertEqual("");
723 | }
724 |
725 | /**
726 | * Generate a string containing the parameter signature of the passed function.
727 | *
728 | * Params:
729 | * func = The function to inspect.
730 | */
731 | private template MethodParameterSignature(func...) if (func.length == 1 && isCallable!(func))
732 | {
733 | private string getMethodParameterSignature()
734 | {
735 | string[] storageClasses = MethodParameterStorageClasses!(func);
736 | string[] types = MethodParameterTypes!(func);
737 | string[] parameters;
738 |
739 | foreach (storageClass, type; zip(storageClasses, types))
740 | {
741 | parameters ~= format("%s%s", storageClass, type);
742 | }
743 |
744 | return parameters.join(", ");
745 | }
746 | enum MethodParameterSignature = getMethodParameterSignature();
747 | }
748 |
749 | unittest
750 | {
751 | class T
752 | {
753 | public void method1(const int foo, string bar){}
754 | public void method2(string baz, bool qux){}
755 | public void method3(ref char quux){}
756 | }
757 |
758 | MethodParameterSignature!(T.method1).assertEqual("const(int), string");
759 | MethodParameterSignature!(T.method2).assertEqual("string, bool");
760 | MethodParameterSignature!(T.method3).assertEqual("ref char");
761 | }
762 |
763 | /**
764 | * Generate a string containing the signature of the passed function.
765 | *
766 | * Params:
767 | * func = The function to inspect.
768 | */
769 | private template MethodSignature(func...) if (func.length == 1 && isCallable!(func))
770 | {
771 | private string getMethodSignature()
772 | {
773 | return format("%s:%s(%s)", MethodReturnType!(func), MethodName!(func), MethodParameterSignature!(func));
774 | }
775 | enum MethodSignature = getMethodSignature();
776 | }
777 |
778 | unittest
779 | {
780 | class T
781 | {
782 | public int method1(const int foo, string bar){return 1;}
783 | public void method2(string baz, bool qux){}
784 | public bool method3(ref char quux){return true;}
785 | }
786 |
787 | MethodSignature!(T.method1).assertEqual("int:method1(const(int), string)");
788 | MethodSignature!(T.method2).assertEqual("void:method2(string, bool)");
789 | MethodSignature!(T.method3).assertEqual("bool:method3(ref char)");
790 | }
791 |
792 | /**
793 | * Generate a string containing the delegate signature of the passed function.
794 | *
795 | * Bugs:
796 | * The 'ref' attribute is not supported in the delegate signature due to a compiler bug.
797 | * Once this bug is fixed we can enable it. http://d.puremagic.com/issues/show_bug.cgi?id=5050
798 | *
799 | * Params:
800 | * func = The function to inspect.
801 | */
802 | private template MethodDelegateSignature(func...) if (func.length == 1 && isCallable!(func))
803 | {
804 | private string getMethodDelegateSignature()
805 | {
806 | return format("%s delegate(%s) %s", MethodReturnType!(func), MethodParameterSignature!(func), MethodAttributes!(func))
807 | .replace(" ref", "")
808 | .stripRight();
809 | }
810 | enum MethodDelegateSignature = getMethodDelegateSignature();
811 | }
812 |
813 | unittest
814 | {
815 | interface T
816 | {
817 | public void method1(const int foo, string bar) @safe pure nothrow;
818 | public void method2(string baz, bool qux);
819 | public void method3(ref char quux);
820 | public ref int method4(string bux);
821 | }
822 |
823 | MethodDelegateSignature!(T.method1).assertEqual("void delegate(const(int), string) @safe pure nothrow");
824 | MethodDelegateSignature!(T.method2).assertEqual("void delegate(string, bool)");
825 | MethodDelegateSignature!(T.method3).assertEqual("void delegate(ref char)");
826 | MethodDelegateSignature!(T.method4).assertEqual("int delegate(string)");
827 | }
828 |
829 | /**
830 | * Generate a string containing the signature switch.
831 | *
832 | * Params:
833 | * func = The function to inspect.
834 | */
835 | private template MethodSignatureSwitch(func...) if (func.length == 1 && isCallable!(func))
836 | {
837 | private string getMethodSignatureSwitch()
838 | {
839 | string code = "";
840 | code ~= "case \"" ~ MethodSignature!(func) ~ "\":\n";
841 | code ~= "\tthis." ~ MethodMangledName!(func) ~ " = cast(" ~ MethodDelegateSignature!(func) ~ ")delegate_;\n";
842 | code ~= "\tbreak;\n";
843 | return code;
844 | }
845 | enum MethodSignatureSwitch = getMethodSignatureSwitch();
846 | }
847 |
--------------------------------------------------------------------------------
/source/dunit/result/moduleresult.d:
--------------------------------------------------------------------------------
1 | /**
2 | * Module to contain the module result class.
3 | *
4 | * License:
5 | * MIT. See LICENSE for full details.
6 | */
7 | module dunit.result.moduleresult;
8 |
9 | /**
10 | * Imports.
11 | */
12 | import dunit.error;
13 |
14 | /**
15 | * A class to contain the result from running a module's unit tests.
16 | */
17 | class ModuleResult
18 | {
19 | /**
20 | * The name of the module which ran the unit test.
21 | */
22 | private string _source;
23 |
24 | /**
25 | * The first error that occurred. If this is null, no errors occurred when running the modules unit tests.
26 | * A module stops running any remaining unit tests if one throws an error.
27 | */
28 | private DUnitAssertError _error;
29 |
30 | /**
31 | * Constructor.
32 | *
33 | * Params:
34 | * name = The name of the module who's unit tests where run.
35 | * error = An error, if it occurred.
36 | */
37 | this(string name, DUnitAssertError error = null)
38 | {
39 | this._source = name;
40 | this._error = error;
41 | }
42 |
43 | /**
44 | * Access the name of the module.
45 | *
46 | * Returns:
47 | * The name of the module that produced this result.
48 | */
49 | public @property string source()
50 | {
51 | return this._source;
52 | }
53 |
54 | /**
55 | * Access the error if one occurred.
56 | *
57 | * Returns:
58 | * The error if one occurred, null if not.
59 | */
60 | public @property DUnitAssertError error()
61 | {
62 | return this._error;
63 | }
64 | }
65 |
66 | unittest
67 | {
68 | import dunit.toolkit;
69 |
70 | auto result = new ModuleResult("Module", new DUnitAssertError("Message", "file.d", 1));
71 | result.source.assertEqual("Module");
72 | result.error.msg.assertEqual("Message");
73 | result.error.file.assertEqual("file.d");
74 | result.error.line.assertEqual(1);
75 | result.error.log.assertEqual([]);
76 | }
77 |
--------------------------------------------------------------------------------
/source/dunit/result/moduleresultcollection.d:
--------------------------------------------------------------------------------
1 | /**
2 | * Module for the module result collection.
3 | *
4 | * License:
5 | * MIT. See LICENSE for full details.
6 | */
7 | module dunit.result.moduleresultcollection;
8 |
9 | /**
10 | * Imports.
11 | */
12 | import dunit.result.moduleresult;
13 | import std.algorithm;
14 | import std.range;
15 |
16 | /**
17 | * A class to hold module results.
18 | */
19 | class ModuleResultCollection
20 | {
21 | /**
22 | * Collection of module results.
23 | */
24 | private ModuleResult[] _results;
25 |
26 | /**
27 | * Indicate if the collection is empty.
28 | *
29 | * Returns:
30 | * true if the collection is empty, false if not.
31 | */
32 | public bool empty()
33 | {
34 | return !this._results.length;
35 | }
36 |
37 | /**
38 | * The total number of tests in the collection.
39 | *
40 | * Returns:
41 | * the number of tests that dunit has run.
42 | */
43 | public size_t totalCount()
44 | {
45 | return this._results.length;
46 | }
47 |
48 | /**
49 | * The amount of tests that contain a DUnitAssertError.
50 | *
51 | * Returns:
52 | * the number of tests that have failed.
53 | */
54 | public size_t failedCount()
55 | {
56 | return this._results.count!(result => result.error !is null);
57 | }
58 |
59 | /**
60 | * The amount of tests that don't contain a DUnitAssertError.
61 | *
62 | * Returns:
63 | * the number of tests that have passed.
64 | */
65 | public size_t passedCount()
66 | {
67 | return this._results.count!(result => result.error is null);
68 | }
69 |
70 | /**
71 | * Add a result to the collection.
72 | *
73 | * This method also sorts the collection by source and makes sure all results containing errors are at the end.
74 | * This enables the console output to be more user friendly.
75 | *
76 | * Params:
77 | * result = The module result to add.
78 | */
79 | public void add(ModuleResult result)
80 | {
81 | this._results ~= result;
82 | this._results.multiSort!("a.error is null && b.error !is null", "a.source < b.source")();
83 | }
84 |
85 | /**
86 | * Overload slicing.
87 | *
88 | * Returns:
89 | * The internal collection of module results.
90 | */
91 | public ModuleResult[] opSlice()
92 | {
93 | return this._results;
94 | }
95 |
96 | /**
97 | * Overload indexing.
98 | *
99 | * Params:
100 | * index = The index of the collection.
101 | *
102 | * Returns:
103 | * The module result residing at the passed index.
104 | */
105 | public ModuleResult opIndex(size_t index)
106 | {
107 | return this._results[index];
108 | }
109 | }
110 |
111 | unittest
112 | {
113 | import dunit.error;
114 | import dunit.toolkit;
115 |
116 | auto results = new ModuleResultCollection();
117 | results.empty().assertTrue();
118 |
119 | results.add(new ModuleResult("Module1"));
120 | results.totalCount().assertEqual(1);
121 | results.failedCount().assertEqual(0);
122 | results.passedCount().assertEqual(1);
123 |
124 | results.add(new ModuleResult("Module2", new DUnitAssertError("Message", "file.d", 1)));
125 | results.totalCount().assertEqual(2);
126 | results.failedCount().assertEqual(1);
127 | results.passedCount().assertEqual(1);
128 |
129 | results.empty().assertFalse();
130 | results[].assertCount(2);
131 |
132 | results[0].source.assertEqual("Module1");
133 | results[0].error.assertNull();
134 | results[1].source.assertEqual("Module2");
135 | results[1].error.assertType!(DUnitAssertError)();
136 | }
137 |
--------------------------------------------------------------------------------
/source/dunit/toolkit.d:
--------------------------------------------------------------------------------
1 | /**
2 | * Assert toolkit for more expressive unit testing.
3 | *
4 | * Many methods implement compile-time parameters (file, line) that are set at the call site.
5 | * It is preferred that these parameters are ignored when using these methods.
6 | *
7 | * License:
8 | * MIT. See LICENSE for full details.
9 | */
10 | module dunit.toolkit;
11 |
12 | /**
13 | * Imports.
14 | */
15 | import dunit.error;
16 | import dunit.moduleunittester;
17 | import std.algorithm;
18 | import std.array;
19 | import std.math;
20 | import std.regex;
21 | import std.stdio;
22 | import std.string;
23 | import std.traits;
24 | import std.range;
25 |
26 | /**
27 | * Assert that two floating point values are approximately equal.
28 | *
29 | * See_Also:
30 | * $(LINK http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm)
31 | *
32 | * Params:
33 | * value = The value used during the assertion.
34 | * target = The target value.
35 | * ulps = The maximum space between two approximately equal floating point numbers measured in $(LINK2 http://en.wikipedia.org/wiki/Unit_in_the_last_place, units of least precision). A higher number means more approximation.
36 | * message = The error message to display.
37 | * file = The file name where the error occurred. The value is added automatically at the call site.
38 | * line = The line where the error occurred. The value is added automatically at the call site.
39 | *
40 | * Throws:
41 | * DUnitAssertError if the assertation fails.
42 | */
43 | public void assertApprox(A, B)(A value, B target, long ulps = 10, string message = "Failed asserting approximately equal", string file = __FILE__, size_t line = __LINE__) if (isFloatingPoint!(CommonType!(A, B)))
44 | {
45 | static if (is(CommonType!(A, B) == double))
46 | {
47 | long maximumUlps = 0x8000000000000;
48 | long negativeZeroFloat = 0x8000000000000000;
49 | long intValue = *(cast(ulong*)&value);
50 | long intTarget = *(cast(ulong*)&target);
51 | long difference;
52 | }
53 | else
54 | {
55 | int maximumUlps = 0x400000;
56 | int negativeZeroFloat = 0x80000000;
57 | int intValue = *(cast(int*)&value);
58 | int intTarget = *(cast(int*)&target);
59 | int difference;
60 | }
61 |
62 | ulps.assertGreaterThan(0, "Unit of least precision should be above 0", file, line);
63 | ulps.assertLessThan(maximumUlps, format("Unit of least precision should be below %s", maximumUlps), file, line);
64 |
65 | if (intValue < 0)
66 | {
67 | intValue = negativeZeroFloat - intValue;
68 | }
69 |
70 | if (intTarget < 0)
71 | {
72 | intTarget = negativeZeroFloat - intTarget;
73 | }
74 |
75 | difference = abs(intValue - intTarget);
76 |
77 | if (difference > ulps)
78 | {
79 | auto error = new DUnitAssertError(message, file, line);
80 |
81 | error.addTypedExpectation("Expected value", target);
82 | error.addTypedError("Actual value", value);
83 |
84 | throw error;
85 | }
86 | }
87 |
88 | ///
89 | unittest
90 | {
91 | float smallestFloatSubnormal = float.min_normal * float.epsilon;
92 | smallestFloatSubnormal.assertApprox(-smallestFloatSubnormal);
93 |
94 | double smallestDoubleSubnormal = double.min_normal * double.epsilon;
95 | smallestDoubleSubnormal.assertApprox(-smallestDoubleSubnormal);
96 |
97 | 0.0f.assertApprox(-0.0f);
98 | (-0.0f).assertApprox(0.0f);
99 | 0.0.assertApprox(-0.0);
100 | (-0.0).assertApprox(0.0);
101 | 2.0f.assertApprox(1.999999f);
102 | 1.999999f.assertApprox(2.0f);
103 | 2.0.assertApprox(1.999999999999999);
104 | 1.999999999999999.assertApprox(2.0);
105 |
106 | // The following tests pass but are open for debate whether or not they should.
107 | float.max.assertApprox(float.infinity);
108 | float.infinity.assertApprox(float.max);
109 | double.infinity.assertApprox(double.max);
110 | double.max.assertApprox(double.infinity);
111 | float.nan.assertApprox(float.nan);
112 | double.nan.assertApprox(double.nan);
113 |
114 | // Assert a DUnitAssertError is thrown if assertApprox fails.
115 | 10f.assertApprox(0f).assertThrow!(DUnitAssertError)("Failed asserting approximately equal");
116 | }
117 |
118 | /**
119 | * Assert that two floating point values are approximately equal using an epsilon value.
120 | *
121 | * See_Also:
122 | * $(LINK http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm)
123 | *
124 | * Params:
125 | * value = The value used during the assertion.
126 | * target = The target value.
127 | * epsilon = An epsilon value to be used as the maximum absolute and relative error in the comparison.
128 | * message = The error message to display.
129 | * file = The file name where the error occurred. The value is added automatically at the call site.
130 | * line = The line where the error occurred. The value is added automatically at the call site.
131 | *
132 | * Throws:
133 | * DUnitAssertError if the assertation fails.
134 | */
135 | public void assertApprox(A, B)(A value, B target, double epsilon, string message = "Failed asserting approximately equal", string file = __FILE__, size_t line = __LINE__) if (isFloatingPoint!(CommonType!(A, B)))
136 | {
137 | auto divisor = (fabs(value) > fabs(target)) ? value : target;
138 | auto absoluteError = fabs(value - target);
139 | auto relativeError = fabs((value - target) / divisor);
140 |
141 | if (absoluteError > epsilon && relativeError > epsilon)
142 | {
143 | auto error = new DUnitAssertError(message, file, line);
144 |
145 | error.addTypedExpectation("Expected value", target);
146 | error.addTypedError("Actual value", value);
147 |
148 | throw error;
149 | }
150 | }
151 |
152 | ///
153 | unittest
154 | {
155 | float smallestFloatSubnormal = float.min_normal * float.epsilon;
156 | smallestFloatSubnormal.assertApprox(-smallestFloatSubnormal, 0.00001);
157 |
158 | double smallestDoubleSubnormal = double.min_normal * double.epsilon;
159 | smallestDoubleSubnormal.assertApprox(-smallestDoubleSubnormal, 0.00001);
160 |
161 | 0.0f.assertApprox(-0.0f, 0.00001);
162 | (-0.0f).assertApprox(0.0f, 0.00001);
163 | 0.0.assertApprox(-0.0, 0.00001);
164 | (-0.0).assertApprox(0.0, 0.00001);
165 | 2.0f.assertApprox(1.99f, 0.01);
166 | 1.99f.assertApprox(2.0f, 0.01);
167 | 2.0.assertApprox(1.99, 0.01);
168 | 1.99.assertApprox(2.0, 0.01);
169 |
170 | // The following tests pass but are open for debate whether or not they should.
171 | float.max.assertApprox(float.infinity, 0.00001);
172 | float.infinity.assertApprox(float.max, 0.00001);
173 | double.infinity.assertApprox(double.max, 0.00001);
174 | double.max.assertApprox(double.infinity, 0.00001);
175 | float.nan.assertApprox(float.nan, 0.00001);
176 | double.nan.assertApprox(double.nan, 0.00001);
177 |
178 | // Assert a DUnitAssertError is thrown if assertApprox fails.
179 | 10f.assertApprox(0f, 0.00001).assertThrow!(DUnitAssertError)("Failed asserting approximately equal");
180 | }
181 |
182 | /**
183 | * Assert that an array contains a particular value count.
184 | *
185 | * Params:
186 | * array = The array to interogate.
187 | * count = The amount of values the array should hold.
188 | * message = The error message to display.
189 | * file = The file name where the error occurred. The value is added automatically at the call site.
190 | * line = The line where the error occurred. The value is added automatically at the call site.
191 | *
192 | * Throws:
193 | * DUnitAssertError if the assertation fails.
194 | */
195 | public void assertCount(A)(A array, ulong count, string message = "Failed asserting array count", string file = __FILE__, size_t line = __LINE__) if (isArray!(A) || isAssociativeArray!(A))
196 | {
197 | if (array.length != count)
198 | {
199 | auto error = new DUnitAssertError(message, file, line);
200 |
201 | error.addInfo("Elements", array);
202 | error.addExpectation("Expected count", count);
203 | error.addError("Actual count", array.length);
204 |
205 | throw error;
206 | }
207 | }
208 |
209 | ///
210 | unittest
211 | {
212 | int[string] associativeArray;
213 | int[] dynamicArray;
214 | string string_;
215 |
216 | associativeArray.assertCount(0);
217 | dynamicArray.assertCount(0);
218 | string_.assertCount(0);
219 | [].assertCount(0);
220 |
221 | "Hello".assertCount(5);
222 | [1, 2, 3, 4].assertCount(4);
223 | ["foo", "bar", "baz", "qux"].assertCount(4);
224 | [["foo", "bar"], ["baz", "qux"]].assertCount(2);
225 | ["foo":1, "bar":2, "baz":3, "qux":4].assertCount(4);
226 |
227 | // Assert a DUnitAssertError is thrown if assertCount fails.
228 | associativeArray.assertCount(1).assertThrow!(DUnitAssertError)("Failed asserting array count");
229 | }
230 |
231 | /**
232 | * Assert that an array is empty.
233 | *
234 | * Params:
235 | * array = The array to interogate.
236 | * message = The error message to display.
237 | * file = The file name where the error occurred. The value is added automatically at the call site.
238 | * line = The line where the error occurred. The value is added automatically at the call site.
239 | *
240 | * Throws:
241 | * DUnitAssertError if the assertation fails.
242 | */
243 | public void assertEmpty(A)(A array, string message = "Failed asserting empty array", string file = __FILE__, size_t line = __LINE__) if (isArray!(A) || isAssociativeArray!(A))
244 | {
245 | if (array.length > 0)
246 | {
247 | auto error = new DUnitAssertError(message, file, line);
248 |
249 | error.addInfo("Elements", array);
250 | error.addExpectation("Expected count", 0);
251 | error.addError("Actual count", array.length);
252 |
253 | throw error;
254 | }
255 | }
256 |
257 | ///
258 | unittest
259 | {
260 | int[string] associativeArray;
261 | int[] dynamicArray;
262 | string string_;
263 |
264 | associativeArray.assertEmpty();
265 | dynamicArray.assertEmpty();
266 | string_.assertEmpty();
267 | [].assertEmpty();
268 |
269 | // Assert a DUnitAssertError is thrown if assertEmpty fails.
270 | [1].assertEmpty().assertThrow!(DUnitAssertError)("Failed asserting empty array");
271 | }
272 |
273 | /**
274 | * Assert that a range is empty.
275 | *
276 | * Params:
277 | * range = The range to interogate.
278 | * message = The error message to display.
279 | * file = The file name where the error occurred. The value is added automatically at the call site.
280 | * line = The line where the error occurred. The value is added automatically at the call site.
281 | *
282 | * Throws:
283 | * DUnitAssertError if the assertation fails.
284 | */
285 | public void assertEmpty(R)(R range, string message = "Failed asserting empty range", string file = __FILE__, size_t line = __LINE__) if ((is(R == class) || is (R == struct)) && isInputRange!(R))
286 | {
287 | if (!range.empty)
288 | {
289 | auto error = new DUnitAssertError(message, file, line);
290 |
291 | error.addExpectation("Expected count", 0);
292 | error.addInfo("Front elements", range.take(10));
293 |
294 | static if (isInfinite!(R))
295 | {
296 | error.addError("Actual count", "∞");
297 | }
298 | else
299 | {
300 | error.addError("Actual count", range.walkLength());
301 | }
302 |
303 | throw error;
304 | }
305 | }
306 |
307 | ///
308 | unittest
309 | {
310 | class A
311 | {
312 | void popFront() {};
313 | @property bool empty() { return true; };
314 | @property int front() { return 0; };
315 | }
316 |
317 | struct B
318 | {
319 | void popFront() {};
320 | enum bool empty = false;
321 | @property int front() { return 1; };
322 | }
323 |
324 | static assert(isInputRange!(A));
325 | static assert(isInputRange!(B));
326 |
327 | auto emptyRange = new A();
328 | emptyRange.assertEmpty();
329 |
330 | B infiniteRange = B.init;
331 | infiniteRange.takeNone.assertEmpty();
332 |
333 | // Assert a DUnitAssertError is thrown if assertEmpty fails.
334 | infiniteRange.assertEmpty().assertThrow!(DUnitAssertError)("Failed asserting empty range");
335 | infiniteRange.take(10).assertEmpty().assertThrow!(DUnitAssertError)("Failed asserting empty range");
336 | }
337 |
338 | /**
339 | * Assert that a string ends with a particular string.
340 | *
341 | * Params:
342 | * value = The value used during the assertion.
343 | * suffix = The suffix to match.
344 | * message = The error message to display.
345 | * file = The file name where the error occurred. The value is added automatically at the call site.
346 | * line = The line where the error occurred. The value is added automatically at the call site.
347 | *
348 | * Throws:
349 | * DUnitAssertError if the assertation fails.
350 | */
351 | public void assertEndsWith(string value, string suffix, string message = "Failed asserting ends with", string file = __FILE__, size_t line = __LINE__)
352 | {
353 | if (!endsWith(value, suffix))
354 | {
355 | auto error = new DUnitAssertError(message, file, line);
356 |
357 | error.addExpectation("Expected end", "..." ~ suffix);
358 | error.addError("Actual value", value);
359 |
360 | throw error;
361 | }
362 | }
363 |
364 | ///
365 | unittest
366 | {
367 | "foo bar".assertEndsWith("bar");
368 | "baz qux".assertEndsWith("qux");
369 |
370 | // Assert a DUnitAssertError is thrown if assertEndsWith fails.
371 | "foo".assertEndsWith("bar").assertThrow!(DUnitAssertError)("Failed asserting ends with");
372 | }
373 |
374 | /**
375 | * Assert that two values are equal.
376 | *
377 | * Params:
378 | * value = The value used during the assertion.
379 | * target = The target value.
380 | * message = The error message to display.
381 | * file = The file name where the error occurred. The value is added automatically at the call site.
382 | * line = The line where the error occurred. The value is added automatically at the call site.
383 | *
384 | * Throws:
385 | * DUnitAssertError if the assertation fails.
386 | */
387 | public void assertEqual(A, B)(A value, B target, string message = "Failed asserting equal", string file = __FILE__, size_t line = __LINE__)
388 | {
389 | if (target != value)
390 | {
391 | auto error = new DUnitAssertError(message, file, line);
392 |
393 | error.addTypedExpectation("Expected value", target);
394 | error.addTypedError("Actual value", value);
395 |
396 | throw error;
397 | }
398 | }
399 |
400 | ///
401 | unittest
402 | {
403 | 123.assertEqual(123);
404 | "hello".assertEqual("hello");
405 |
406 | // Assert a DUnitAssertError is thrown if assertEqual fails.
407 | 1.assertEqual(2).assertThrow!(DUnitAssertError)("Failed asserting equal");
408 | }
409 |
410 | /**
411 | * Assert that a boolean value is false.
412 | *
413 | * Params:
414 | * value = The value used during the assertion.
415 | * message = The error message to display.
416 | * file = The file name where the error occurred. The value is added automatically at the call site.
417 | * line = The line where the error occurred. The value is added automatically at the call site.
418 | *
419 | * Throws:
420 | * DUnitAssertError if the assertation fails.
421 | */
422 | public void assertFalse(T)(T value, string message = "Failed asserting false", string file = __FILE__, size_t line = __LINE__)
423 | {
424 | value.assertType!(bool)("Wrong type for asserting false", file, line);
425 |
426 | if (value)
427 | {
428 | auto error = new DUnitAssertError(message, file, line);
429 |
430 | error.addError("Value", value);
431 |
432 | throw error;
433 | }
434 | }
435 |
436 | ///
437 | unittest
438 | {
439 | false.assertFalse();
440 |
441 | // Assert a DUnitAssertError is thrown if assertFalse fails.
442 | true.assertFalse().assertThrow!(DUnitAssertError)("Failed asserting false");
443 | }
444 |
445 | /**
446 | * Assert that a value evaluates as false.
447 | *
448 | * Params:
449 | * value = The value used during the assertion.
450 | * message = The error message to display.
451 | * file = The file name where the error occurred. The value is added automatically at the call site.
452 | * line = The line where the error occurred. The value is added automatically at the call site.
453 | *
454 | * Throws:
455 | * DUnitAssertError if the assertation fails.
456 | */
457 | public void assertFalsey(T)(T value, string message = "Failed asserting falsey", string file = __FILE__, size_t line = __LINE__)
458 | {
459 | if (value)
460 | {
461 | auto error = new DUnitAssertError(message, file, line);
462 |
463 | error.addTypedInfo("Value", value);
464 | error.addError("Evaluates to", !!value);
465 |
466 | throw error;
467 | }
468 | }
469 |
470 | ///
471 | unittest
472 | {
473 | false.assertFalsey();
474 | [].assertFalsey();
475 | null.assertFalsey();
476 | 0.assertFalsey();
477 |
478 | // Assert a DUnitAssertError is thrown if assertFalsey fails.
479 | true.assertFalsey().assertThrow!(DUnitAssertError)("Failed asserting falsey");
480 | }
481 |
482 | /**
483 | * Assert that a value is greater than a threshold value.
484 | *
485 | * Params:
486 | * value = The value used during the assertion.
487 | * threshold = The threshold value.
488 | * message = The error message to display.
489 | * file = The file name where the error occurred. The value is added automatically at the call site.
490 | * line = The line where the error occurred. The value is added automatically at the call site.
491 | *
492 | * Throws:
493 | * DUnitAssertError if the assertation fails.
494 | */
495 | public void assertGreaterThan(A, B)(A value, B threshold, string message = "Failed asserting greater than", string file = __FILE__, size_t line = __LINE__)
496 | {
497 | if (value <= threshold)
498 | {
499 | auto error = new DUnitAssertError(message, file, line);
500 |
501 | error.addExpectation("Minimum value", threshold + 1);
502 | error.addError("Actual value", value);
503 |
504 | throw error;
505 | }
506 | }
507 |
508 | ///
509 | unittest
510 | {
511 | 11.assertGreaterThan(10);
512 |
513 | // Assert a DUnitAssertError is thrown if assertGreaterThan fails.
514 | 11.assertGreaterThan(12).assertThrow!(DUnitAssertError)("Failed asserting greater than");
515 | }
516 |
517 | /**
518 | * Assert that a value is greater than or equal to a threshold value.
519 | *
520 | * Params:
521 | * value = The value used during the assertion.
522 | * threshold = The threshold value.
523 | * message = The error message to display.
524 | * file = The file name where the error occurred. The value is added automatically at the call site.
525 | * line = The line where the error occurred. The value is added automatically at the call site.
526 | *
527 | * Throws:
528 | * DUnitAssertError if the assertation fails.
529 | */
530 | public void assertGreaterThanOrEqual(A, B)(A value, B threshold, string message = "Failed asserting greater than or equal", string file = __FILE__, size_t line = __LINE__)
531 | {
532 | if (value < threshold)
533 | {
534 | auto error = new DUnitAssertError(message, file, line);
535 |
536 | error.addExpectation("Minimum value", threshold);
537 | error.addError("Actual value", value);
538 |
539 | throw error;
540 | }
541 | }
542 |
543 | ///
544 | unittest
545 | {
546 | 10.assertGreaterThanOrEqual(10);
547 | 11.assertGreaterThanOrEqual(10);
548 |
549 | // Assert a DUnitAssertError is thrown if assertGreaterThanOrEqual fails.
550 | 11.assertGreaterThanOrEqual(12).assertThrow!(DUnitAssertError)("Failed asserting greater than or equal");
551 | }
552 |
553 | /**
554 | * Assert that an associative array contains a particular key.
555 | *
556 | * Params:
557 | * haystack = The associative array to interogate.
558 | * needle = The key the array should contain.
559 | * message = The error message to display.
560 | * file = The file name where the error occurred. The value is added automatically at the call site.
561 | * line = The line where the error occurred. The value is added automatically at the call site.
562 | *
563 | * Throws:
564 | * DUnitAssertError if the assertation fails.
565 | */
566 | public void assertHasKey(A, B)(A haystack, B needle, string message = "Failed asserting array has key", string file = __FILE__, size_t line = __LINE__) if (isAssociativeArray!(A))
567 | {
568 | if (needle !in haystack)
569 | {
570 | auto error = new DUnitAssertError(message, file, line);
571 |
572 | error.addInfo("Array type", typeof(haystack).stringof);
573 | error.addInfo("Elements", haystack);
574 | error.addTypedError("Missing key", needle);
575 |
576 | throw error;
577 | }
578 | }
579 |
580 | ///
581 | unittest
582 | {
583 | ["foo":1, "bar":2, "baz":3, "qux":4].assertHasKey("foo");
584 | [1:"foo", 2:"bar", 3:"baz", 4:"qux"].assertHasKey(1);
585 |
586 | // Assert a DUnitAssertError is thrown if assertHasKey fails.
587 | ["foo":"bar"].assertHasKey("baz").assertThrow!(DUnitAssertError)("Failed asserting array has key");
588 | }
589 |
590 | /**
591 | * Assert that an array contains a particular value.
592 | *
593 | * Params:
594 | * haystack = The array to interogate.
595 | * needle = The value the array should contain.
596 | * message = The error message to display.
597 | * file = The file name where the error occurred. The value is added automatically at the call site.
598 | * line = The line where the error occurred. The value is added automatically at the call site.
599 | *
600 | * Throws:
601 | * DUnitAssertError if the assertation fails.
602 | */
603 | public void assertHasValue(A, B)(A haystack, B needle, string message = "Failed asserting array has value", string file = __FILE__, size_t line = __LINE__) if (isArray!(A) || isAssociativeArray!(A))
604 | {
605 | static if (isArray!(A))
606 | {
607 | bool foundValue = canFind(haystack, needle);
608 | }
609 | else static if (isAssociativeArray!(A))
610 | {
611 | bool foundValue = canFind(haystack.values, needle);
612 | }
613 |
614 | if (!foundValue)
615 | {
616 | auto error = new DUnitAssertError(message, file, line);
617 |
618 | error.addInfo("Array type", typeof(haystack).stringof);
619 | error.addInfo("Elements", haystack);
620 | error.addTypedError("Missing value", needle);
621 |
622 | throw error;
623 | }
624 | }
625 |
626 | ///
627 | unittest
628 | {
629 | "Hello".assertHasValue("H");
630 | [1, 2, 3, 4].assertHasValue(2);
631 | ["foo", "bar", "baz", "qux"].assertHasValue("foo");
632 | [["foo", "bar"], ["baz", "qux"]].assertHasValue(["foo", "bar"]);
633 | ["foo":1, "bar":2, "baz":3, "qux":4].assertHasValue(4);
634 |
635 | // Assert a DUnitAssertError is thrown if assertHasValue fails.
636 | ["foo":"bar"].assertHasValue("baz").assertThrow!(DUnitAssertError)("Failed asserting array has value");
637 | }
638 |
639 | /**
640 | * Assert that a value is an instance of a type.
641 | *
642 | * Params:
643 | * value = The value used during the assertion.
644 | * message = The error message to display.
645 | * file = The file name where the error occurred. The value is added automatically at the call site.
646 | * line = The line where the error occurred. The value is added automatically at the call site.
647 | *
648 | * Throws:
649 | * DUnitAssertError if the assertation fails.
650 | */
651 | public void assertInstanceOf(A, B)(B value, string message = "Failed asserting instance of", string file = __FILE__, size_t line = __LINE__)
652 | {
653 | if (!cast(A)value)
654 | {
655 | auto error = new DUnitAssertError(message, file, line);
656 |
657 | error.addExpectation("Expected instance", A.stringof);
658 | error.addError("Non derived type", B.stringof);
659 |
660 | throw error;
661 | }
662 | }
663 |
664 | ///
665 | unittest
666 | {
667 | interface A {}
668 | class B : A {}
669 | class C : B {}
670 |
671 | auto b = new B();
672 | auto c = new C();
673 |
674 | b.assertInstanceOf!(Object)();
675 | b.assertInstanceOf!(A)();
676 | b.assertInstanceOf!(B)();
677 |
678 | c.assertInstanceOf!(Object)();
679 | c.assertInstanceOf!(A)();
680 | c.assertInstanceOf!(B)();
681 | c.assertInstanceOf!(C)();
682 |
683 | // Assert a DUnitAssertError is thrown if assertInstanceOf fails.
684 | b.assertInstanceOf!(C)().assertThrow!(DUnitAssertError)("Failed asserting instance of");
685 | }
686 |
687 | /**
688 | * Assert that a value is less than a threshold value.
689 | *
690 | * Params:
691 | * value = The value used during the assertion.
692 | * threshold = The threshold value.
693 | * message = The error message to display.
694 | * file = The file name where the error occurred. The value is added automatically at the call site.
695 | * line = The line where the error occurred. The value is added automatically at the call site.
696 | *
697 | * Throws:
698 | * DUnitAssertError if the assertation fails.
699 | */
700 | public void assertLessThan(A, B)(A value, B threshold, string message = "Failed asserting less than", string file = __FILE__, size_t line = __LINE__)
701 | {
702 | if (value >= threshold)
703 | {
704 | auto error = new DUnitAssertError(message, file, line);
705 |
706 | error.addExpectation("Maximum value", threshold - 1);
707 | error.addError("Actual value", value);
708 |
709 | throw error;
710 | }
711 | }
712 |
713 | ///
714 | unittest
715 | {
716 | 9.assertLessThan(10);
717 |
718 | // Assert a DUnitAssertError is thrown if assertLessThan fails.
719 | 9.assertLessThan(8).assertThrow!(DUnitAssertError)("Failed asserting less than");
720 | }
721 |
722 | /**
723 | * Assert that a value is less than or equal to a threshold value.
724 | *
725 | * Params:
726 | * value = The value used during the assertion.
727 | * threshold = The threshold value.
728 | * message = The error message to display.
729 | * file = The file name where the error occurred. The value is added automatically at the call site.
730 | * line = The line where the error occurred. The value is added automatically at the call site.
731 | *
732 | * Throws:
733 | * DUnitAssertError if the assertation fails.
734 | */
735 | public void assertLessThanOrEqual(A, B)(A value, B threshold, string message = "Failed asserting less than or equal", string file = __FILE__, size_t line = __LINE__)
736 | {
737 | if (value > threshold)
738 | {
739 | auto error = new DUnitAssertError(message, file, line);
740 |
741 | error.addExpectation("Maximum value", threshold);
742 | error.addError("Actual value", value);
743 |
744 | throw error;
745 | }
746 | }
747 |
748 | ///
749 | unittest
750 | {
751 | 10.assertLessThanOrEqual(10);
752 | 9.assertLessThanOrEqual(10);
753 |
754 | // Assert a DUnitAssertError is thrown if assertLessThanOrEqual fails.
755 | 9.assertLessThanOrEqual(8).assertThrow!(DUnitAssertError)("Failed asserting less than or equal");
756 | }
757 |
758 | /**
759 | * Assert that a string matches a regular expression.
760 | *
761 | * Params:
762 | * value = The value used during the assertion.
763 | * pattern = The regular expression pattern.
764 | * message = The error message to display.
765 | * file = The file name where the error occurred. The value is added automatically at the call site.
766 | * line = The line where the error occurred. The value is added automatically at the call site.
767 | *
768 | * Throws:
769 | * DUnitAssertError if the assertation fails.
770 | */
771 | public void assertMatchRegex(string value, string pattern, string message = "Failed asserting match to regex", string file = __FILE__, size_t line = __LINE__)
772 | {
773 | if (match(value, pattern).empty())
774 | {
775 | auto error = new DUnitAssertError(message, file, line);
776 |
777 | error.addInfo("Regex", pattern);
778 | error.addError("Value", value);
779 |
780 | throw error;
781 | }
782 | }
783 |
784 | ///
785 | unittest
786 | {
787 | "foo".assertMatchRegex(r"^foo$");
788 | "192.168.0.1".assertMatchRegex(r"((?:[\d]{1,3}\.){3}[\d]{1,3})");
789 |
790 | // Assert a DUnitAssertError is thrown if assertMatchRegex fails.
791 | "foo".assertMatchRegex(r"^bar$").assertThrow!(DUnitAssertError)("Failed asserting match to regex");
792 | }
793 |
794 | /**
795 | * Assert that a value is null.
796 | *
797 | * Params:
798 | * value = The value to assert as null.
799 | * message = The error message to display.
800 | * file = The file name where the error occurred. The value is added automatically at the call site.
801 | * line = The line where the error occurred. The value is added automatically at the call site.
802 | *
803 | * Throws:
804 | * DUnitAssertError if the assertation fails.
805 | */
806 | public void assertNull(A)(A value, string message = "Failed asserting null", string file = __FILE__, size_t line = __LINE__) if (A.init is null)
807 | {
808 | if (value !is null)
809 | {
810 | auto error = new DUnitAssertError(message, file, line);
811 |
812 | error.addTypedError("Actual value", value);
813 |
814 | throw error;
815 |
816 | }
817 | }
818 |
819 | ///
820 | unittest
821 | {
822 | class T {}
823 |
824 | string foo;
825 | int[] bar;
826 | T t;
827 |
828 | foo.assertNull();
829 | bar.assertNull();
830 | t.assertNull();
831 | null.assertNull();
832 |
833 | // Assert a DUnitAssertError is thrown if assertNull fails.
834 | "foo".assertNull().assertThrow!(DUnitAssertError)("Failed asserting null");
835 | }
836 |
837 | /**
838 | * Assert that a string starts with a particular string.
839 | *
840 | * Params:
841 | * value = The value used during the assertion.
842 | * prefix = The prefix to match.
843 | * message = The error message to display.
844 | * file = The file name where the error occurred. The value is added automatically at the call site.
845 | * line = The line where the error occurred. The value is added automatically at the call site.
846 | *
847 | * Throws:
848 | * DUnitAssertError if the assertation fails.
849 | */
850 | public void assertStartsWith(string value, string prefix, string message = "Failed asserting starts with", string file = __FILE__, size_t line = __LINE__)
851 | {
852 | if (!startsWith(value, prefix))
853 | {
854 | auto error = new DUnitAssertError(message, file, line);
855 |
856 | error.addExpectation("Expected start", prefix ~ "...");
857 | error.addError("Actual value", value);
858 |
859 | throw error;
860 | }
861 | }
862 |
863 | ///
864 | unittest
865 | {
866 | "foo bar".assertStartsWith("foo");
867 | "baz qux".assertStartsWith("baz");
868 |
869 | // Assert a DUnitAssertError is thrown if assertStartsWith fails.
870 | "foo bar".assertStartsWith("baz").assertThrow!(DUnitAssertError)("Failed asserting starts with");
871 | }
872 |
873 | /**
874 | * Assert that an expression throws an exception.
875 | *
876 | * Params:
877 | * expression = The expression to evaluate in order to assert the exception is thrown.
878 | * expressionMsg = An optional expected message of the thrown exception.
879 | * message = The error message to display.
880 | * file = The file name where the error occurred. The value is added automatically at the call site.
881 | * line = The line where the error occurred. The value is added automatically at the call site.
882 | *
883 | * Throws:
884 | * DUnitAssertError if the assertation fails.
885 | */
886 | public void assertThrow(A : Throwable = Exception, B)(lazy B expression, string expressionMsg = null, string message = "Failed asserting throw", string file = __FILE__, size_t line = __LINE__)
887 | {
888 | try
889 | {
890 | try
891 | {
892 | expression;
893 | }
894 | catch (A ex)
895 | {
896 | if (expressionMsg !is null && expressionMsg != ex.msg)
897 | {
898 | auto error = new DUnitAssertError(message, file, line);
899 |
900 | error.addExpectation("Expected message", expressionMsg);
901 | error.addError("Thrown message", ex.msg);
902 |
903 | throw error;
904 | }
905 | return;
906 | }
907 | }
908 | catch (Exception ex)
909 | {
910 | // If the expression throws an exception other than the one specified just let it pass.
911 | // We can't get any meaningful information about what was thrown anyway.
912 | }
913 |
914 | auto error = new DUnitAssertError(message, file, line);
915 |
916 | error.addError("Expected exception", A.stringof);
917 |
918 | throw error;
919 | }
920 |
921 | ///
922 | unittest
923 | {
924 | import core.exception : AssertError, RangeError;
925 |
926 | class Foo : Exception
927 | {
928 | this(string message)
929 | {
930 | super(message);
931 | }
932 | }
933 |
934 | class Bar
935 | {
936 | public void baz()
937 | {
938 | throw new Foo("Thrown from baz.");
939 | }
940 | }
941 |
942 | auto bar = new Bar();
943 | bar.baz().assertThrow();
944 | bar.baz().assertThrow!(Foo)("Thrown from baz.");
945 |
946 | delegate(){throw new Foo("Thrown from delegate.");}().assertThrow!(Exception)("Thrown from delegate.");
947 |
948 | auto baz = [0, 1, 2];
949 | baz[3].assertThrow!(RangeError)();
950 |
951 | assert(false).assertThrow!(AssertError)("Assertion failure");
952 |
953 | // Assert a DUnitAssertError is thrown if assertThrow fails.
954 | null.assertThrow().assertThrow!(DUnitAssertError)("Failed asserting throw");
955 |
956 | // Assert a DUnitAssertError is thrown if assertThrow fails due to mismatched error message.
957 | baz[3].assertThrow!(RangeError)("Foo").assertThrow!(DUnitAssertError)("Failed asserting throw");
958 | }
959 |
960 | /**
961 | * Assert that a boolean value is true.
962 | *
963 | * Params:
964 | * value = The value used during the assertion.
965 | * message = The error message to display.
966 | * file = The file name where the error occurred. The value is added automatically at the call site.
967 | * line = The line where the error occurred. The value is added automatically at the call site.
968 | *
969 | * Throws:
970 | * DUnitAssertError if the assertation fails.
971 | */
972 | public void assertTrue(T)(T value, string message = "Failed asserting true", string file = __FILE__, size_t line = __LINE__)
973 | {
974 | value.assertType!(bool)("Wrong type for asserting true", file, line);
975 |
976 | if (!value)
977 | {
978 | auto error = new DUnitAssertError(message, file, line);
979 |
980 | error.addError("Value", value);
981 |
982 | throw error;
983 | }
984 | }
985 |
986 | ///
987 | unittest
988 | {
989 | true.assertTrue();
990 |
991 | // Assert a DUnitAssertError is thrown if assertTrue fails.
992 | false.assertTrue().assertThrow!(DUnitAssertError)("Failed asserting true");
993 | }
994 |
995 | /**
996 | * Assert that a value evaluates as true.
997 | *
998 | * Params:
999 | * value = The value used during the assertion.
1000 | * message = The error message to display.
1001 | * file = The file name where the error occurred. The value is added automatically at the call site.
1002 | * line = The line where the error occurred. The value is added automatically at the call site.
1003 | *
1004 | * Throws:
1005 | * DUnitAssertError if the assertation fails.
1006 | */
1007 | public void assertTruthy(T)(T value, string message = "Failed asserting truthy", string file = __FILE__, size_t line = __LINE__)
1008 | {
1009 | if (!value)
1010 | {
1011 | auto error = new DUnitAssertError(message, file, line);
1012 |
1013 | error.addTypedInfo("Value", value);
1014 | error.addError("Evaluates to", !!value);
1015 |
1016 | throw error;
1017 | }
1018 | }
1019 |
1020 | ///
1021 | unittest
1022 | {
1023 | true.assertTruthy();
1024 | ["foo"].assertTruthy();
1025 | 1.assertTruthy();
1026 |
1027 | // Assert a DUnitAssertError is thrown if assertTruthy fails.
1028 | false.assertTruthy().assertThrow!(DUnitAssertError)("Failed asserting truthy");
1029 | }
1030 |
1031 | /**
1032 | * Assert that a value is of a particular type.
1033 | *
1034 | * Params:
1035 | * value = The value used during the assertion.
1036 | * message = The error message to display.
1037 | * file = The file name where the error occurred. The value is added automatically at the call site.
1038 | * line = The line where the error occurred. The value is added automatically at the call site.
1039 | *
1040 | * Throws:
1041 | * DUnitAssertError if the assertation fails.
1042 | */
1043 | public void assertType(A, B)(B value, string message = "Failed asserting type", string file = __FILE__, size_t line = __LINE__)
1044 | {
1045 | if (!is(A == B))
1046 | {
1047 | auto error = new DUnitAssertError(message, file, line);
1048 |
1049 | error.addExpectation("Expected type", A.stringof);
1050 | error.addError("Actual type", B.stringof);
1051 |
1052 | throw error;
1053 | }
1054 | }
1055 |
1056 | ///
1057 | unittest
1058 | {
1059 | 1.assertType!(int)();
1060 | "foo".assertType!(string)();
1061 | ["bar"].assertType!(string[])();
1062 | ['a'].assertType!(char[])();
1063 |
1064 | // Assert a DUnitAssertError is thrown if assertType fails.
1065 | false.assertType!(string)().assertThrow!(DUnitAssertError)("Failed asserting type");
1066 | }
1067 |
--------------------------------------------------------------------------------
/source/example.d:
--------------------------------------------------------------------------------
1 | /**
2 | * Imports.
3 | */
4 | import dunit.mockable;
5 | import std.algorithm;
6 |
7 | /**
8 | * Simple class representing a person.
9 | */
10 | class Person
11 | {
12 | private string _name;
13 | private int _age;
14 |
15 | this()
16 | {
17 | }
18 |
19 | this(string name, int age)
20 | {
21 | this._name = name;
22 | this._age = age;
23 | }
24 |
25 | public string getName()
26 | {
27 | return this._name;
28 | }
29 |
30 | public int getAge()
31 | {
32 | return this._age;
33 | }
34 |
35 | // Mixin mocking behaviour.
36 | mixin Mockable!(Person);
37 | }
38 |
39 | /**
40 | * Processor class that uses Person as a dependency.
41 | */
42 | class Processor
43 | {
44 | private Person[] _people;
45 |
46 | public void addPerson(Person person)
47 | {
48 | this._people ~= person;
49 | }
50 |
51 | public ulong getAmountOfPeople()
52 | {
53 | return this._people.length;
54 | }
55 |
56 | public float getMeanAge()
57 | {
58 | return cast(float)reduce!((a, b) => a + b.getAge())(0, this._people) / this.getAmountOfPeople();
59 | }
60 | }
61 |
62 | unittest
63 | {
64 | import dunit.toolkit;
65 |
66 | // Create mock people.
67 | auto gary = Person.getMock();
68 | gary.disableParentMethods();
69 |
70 | auto tessa = Person.getMock();
71 | tessa.disableParentMethods();
72 |
73 | // Mock the getAge method to return 40. Set the minimum count to 1 and the maximum count to 2.
74 | gary.mockMethod("getAge", delegate(){
75 | return 40;
76 | }, 1, 2);
77 |
78 | // Mock the getAge method to return 34. Set the minimum count to 1 and the maximum count to 2.
79 | tessa.mockMethod("getAge", delegate(){
80 | return 34;
81 | }, 1, 2);
82 |
83 | // Create the object we are unit testing.
84 | auto processor = new Processor();
85 |
86 | // Add mock people to the processor.
87 | processor.addPerson(gary);
88 | processor.addPerson(tessa);
89 |
90 | // Make assertions of the processor, calling the mock methods on the mock class.
91 | processor.getAmountOfPeople().assertEqual(2);
92 | processor.getMeanAge().assertEqual(37);
93 |
94 | // Assert mock method calls are within limits.
95 | gary.assertMethodCalls();
96 | tessa.assertMethodCalls();
97 | }
98 |
--------------------------------------------------------------------------------