├── .github └── workflows │ └── d.yml ├── .gitignore ├── .travis.yml ├── CNAME ├── LICENSE ├── README.md ├── _config.yml ├── api ├── above.md ├── approximately.md ├── basic.md ├── beNull.md ├── below.md ├── between.md ├── callable.md ├── contain.md ├── containOnly.md ├── endWith.md ├── equal.md ├── greaterOrEqualTo.md ├── greaterThan.md ├── instanceOf.md ├── lessOrEqualTo.md ├── lessThan.md ├── objects.md ├── ranges.md ├── startWith.md ├── strings.md ├── throwAnyException.md ├── throwException.md ├── throwSomething.md ├── vibe-json.md ├── vibe-requests.md └── within.md ├── dscanner.ini ├── dub.json ├── source ├── fluent │ └── asserts.d ├── fluentasserts │ └── core │ │ ├── array.d │ │ ├── base.d │ │ ├── basetype.d │ │ ├── callable.d │ │ ├── evaluation.d │ │ ├── expect.d │ │ ├── lifecycle.d │ │ ├── message.d │ │ ├── objects.d │ │ ├── operations │ │ ├── approximately.d │ │ ├── arrayEqual.d │ │ ├── beNull.d │ │ ├── between.d │ │ ├── contain.d │ │ ├── endWith.d │ │ ├── equal.d │ │ ├── greaterOrEqualTo.d │ │ ├── greaterThan.d │ │ ├── instanceOf.d │ │ ├── lessOrEqualTo.d │ │ ├── lessThan.d │ │ ├── registry.d │ │ ├── startWith.d │ │ └── throwable.d │ │ ├── results.d │ │ ├── serializers.d │ │ └── string.d └── updateDocs.d ├── test ├── class.d ├── example.txt ├── operations │ ├── approximately.d │ ├── arrayContain.d │ ├── arrayEqual.d │ ├── beNull.d │ ├── between.d │ ├── contain.d │ ├── containOnly.d │ ├── endWith.d │ ├── equal.d │ ├── greaterOrEqualTo.d │ ├── greaterThan.d │ ├── instanceOf.d │ ├── lessOrEqualTo.d │ ├── lessThan.d │ └── startWith.d ├── test.sh ├── unit-threaded │ ├── .gitignore │ ├── dub.json │ └── source │ │ └── app.d ├── values.d └── vibe-0.8 │ ├── dub.json │ └── source │ └── app.d ├── travis-ci.sh └── trial.json /.github/workflows/d.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | name: D 6 | 7 | on: [push, pull_request] 8 | 9 | jobs: 10 | test: 11 | name: Trial Tests 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macOS-latest] 15 | dc: [ldc-latest, dmd-2.100.0, dmd-2.099.0, dmd-2.098.0, ldc-1.30.0, ldc-1.29.0, ldc-1.28.0] 16 | 17 | runs-on: ${{ matrix.os }} 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: Install D compiler 22 | uses: dlang-community/setup-dlang@v1 23 | with: 24 | compiler: ${{ matrix.dc }} 25 | 26 | - name: Run tests 27 | run: dub run trial:runner 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | *.lst 7 | *.a 8 | generated.d 9 | /fluent-asserts-core-test-library 10 | /fluent-asserts-test-library 11 | /fluent-asserts-vibe-test-library 12 | trial-stats.csv 13 | coverage/ 14 | trial-core 15 | trial-root 16 | trial_core.d 17 | trial_root.d 18 | trial_vibe.d 19 | trial-vibe 20 | vibe-example 21 | .trial 22 | test/unit-threaded/dub.selections.json 23 | test/vibe-0.7/dub.selections.json 24 | test/vibe-0.8/dub.selections.json 25 | test/vibe-0.8/trial.json 26 | dub.selections.json 27 | test/unit-threaded/trial.json 28 | test/unit-threaded/unit-threaded-example 29 | test/disabledDiffResult/disabled-diff-result-example 30 | test/disabledMessageResult/disabled-message-result-example 31 | test/disabledSourceResult/disabled-source-result-example 32 | fluent-asserts-test-unittest 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | sudo: false 3 | dist: focal 4 | 5 | addons: 6 | apt: 7 | packages: 8 | - libevent-dev 9 | - libssl-dev 10 | - pkg-config 11 | - zlib1g-dev 12 | 13 | d: 14 | # order: latest DMD, oldest DMD, LDC/GDC, remaining DMD versions 15 | # this way the overall test time gets cut down (GDC/LDC are a lot 16 | # slower tham DMD, so they should be started early), while still 17 | # catching most DMD version related build failures early 18 | - ldc-1.23.0 19 | - ldc-1.24.0 20 | - ldc-1.25.0 21 | - ldc-beta 22 | - dmd-2.094.0 23 | - dmd-2.095.0 24 | - dmd-2.096.0 25 | - dmd-beta 26 | 27 | matrix: 28 | allow_failures: 29 | - d: dmd-beta 30 | - d: ldc-beta 31 | 32 | script: ./travis-ci.sh 33 | 34 | cache: 35 | directories: 36 | - $HOME/.dub 37 | - .dub 38 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | fluentasserts.szabobogdan.com -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright(c) 2016-2017 Szabo Bogdan 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Line Coverage](https://szabobogdan3.gitlab.io/fluent-asserts-coverage/coverage-shield.svg)](https://szabobogdan3.gitlab.io/fluent-asserts-coverage/) 2 | [![DUB Version](https://img.shields.io/dub/v/fluent-asserts.svg)](https://code.dlang.org/packages/fluent-asserts) 3 | [![DUB Installs](https://img.shields.io/dub/dt/fluent-asserts.svg)](https://code.dlang.org/packages/fluent-asserts) 4 | 5 | [Writing unit tests is easy with Dlang](https://dlang.org/spec/unittest.html). The `unittest` block allows you to start writing tests and to be productive with no special setup. 6 | 7 | Unfortunately the [assert expression](https://dlang.org/spec/expression.html#AssertExpression) does not help you to write expressive asserts, and in case of a failure it's hard to find why an assert failed. The `fluent-asserts` library allows you to more naturally specify the expected outcome of a TDD or BDD-style test. 8 | 9 | ## To begin 10 | 11 | 1. Add the DUB dependency: 12 | [https://code.dlang.org/packages/fluent-asserts](https://code.dlang.org/packages/fluent-asserts) 13 | 14 | ```bash 15 | $ dub add fluent-asserts 16 | ``` 17 | 18 | 2. Use it: 19 | ```D 20 | unittest { 21 | true.should.equal(false).because("this is a failing assert"); 22 | } 23 | 24 | unittest { 25 | Assert.equal(true, false, "this is a failing assert"); 26 | } 27 | ``` 28 | 29 | 3. Run the tests: 30 | ```D 31 | ➜ dub test --compiler=ldc2 32 | ``` 33 | 34 | [![asciicast](https://asciinema.org/a/9x0suc3hanpe67uegtster7o1.png)](https://asciinema.org/a/9x0suc3hanpe67uegtster7o1) 35 | 36 | # API Docs 37 | 38 | The library provides the `expect`, `should` templates and the `Assert` struct. 39 | 40 | 41 | ## Expect 42 | 43 | `expect` is the main assert function exposed by this library. It takes a parameter which is the value that is tested. You can 44 | use any assert operation provided by the base library or any other operations that was registered by a third party library. 45 | 46 | ```D 47 | Expect expect(T)(lazy T testedValue, ...); 48 | Expect expect(void delegate() callable, ...); 49 | ... 50 | 51 | expect(testedValue).to.equal(42); 52 | ``` 53 | 54 | In addition, the library provides the `not` and `because` modifiers that allow to improve your asserts. 55 | 56 | `not` negates the assert condition: 57 | 58 | ```D 59 | expect(testedValue).to.not.equal(42); 60 | ``` 61 | 62 | `because` allows you to add a custom message: 63 | 64 | ```D 65 | expect(true).to.equal(false).because("of test reasons"); 66 | /// will output this message: Because of test reasons, true should equal `false`. 67 | ``` 68 | 69 | 70 | ## Should 71 | 72 | `should` is designed to be used in combination with [Uniform Function Call Syntax (UFCS)](https://dlang.org/spec/function.html#pseudo-member), and 73 | is an alias for `expect`. 74 | 75 | ```D 76 | auto should(T)(lazy T testData, ...); 77 | ``` 78 | 79 | So the following statements are equivalent 80 | 81 | ```D 82 | testedValue.should.equal(42); 83 | expect(testedValue).to.equal(42); 84 | ``` 85 | 86 | In addition, you can use `not` and `because` modifiers with `should`. 87 | 88 | `not` negates the assert condition: 89 | 90 | ```D 91 | testedValue.should.not.equal(42); 92 | true.should.equal(false).because("of test reasons"); 93 | ``` 94 | 95 | ## Assert 96 | 97 | `Assert` is a wrapper for the expect function, that allows you to use the asserts with a different syntax. 98 | 99 | For example, the following lines are equivalent: 100 | ```D 101 | expect(testedValue).to.equal(42); 102 | Assert.equal(testedValue, 42); 103 | ``` 104 | 105 | All the asserts that are available using the `expect` syntax are available with `Assert`. If you want to negate the check, 106 | just add `not` before the assert name: 107 | 108 | ```D 109 | Assert.notEqual(testedValue, 42); 110 | ``` 111 | 112 | ## Built in operations 113 | 114 | - [above](api/above.md) 115 | - [approximately](api/approximately.md) 116 | - [beNull](api/beNull.md) 117 | - [below](api/below.md) 118 | - [between](api/between.md) 119 | - [contain](api/contain.md) 120 | - [containOnly](api/containOnly.md) 121 | - [endWith](api/endWith.md) 122 | - [equal](api/equal.md) 123 | - [greaterOrEqualTo](api/greaterOrEqualTo.md) 124 | - [greaterThan](api/greaterThan.md) 125 | - [instanceOf](api/instanceOf.md) 126 | - [lessOrEqualTo](api/lessOrEqualTo.md) 127 | - [lessThan](api/lessThan.md) 128 | - [startWith](api/startWith.md) 129 | - [throwAnyException](api/throwAnyException.md) 130 | - [throwException](api/throwException.md) 131 | - [throwSomething](api/throwSomething.md) 132 | - [within](api/within.md) 133 | 134 | # Extend the library 135 | 136 | ## Registering new operations 137 | 138 | Even though this library has an extensive set of operations, sometimes a new operation might be needed to test your code. Operations are functions that recieve an `Evaluation` and returns an `IResult` list in case there was a failure. You can check any of the built in operations for a refference implementation. 139 | 140 | ```d 141 | IResult[] customOperation(ref Evaluation evaluation) @safe nothrow { 142 | ... 143 | } 144 | ``` 145 | 146 | Once the operation is ready to use, it has to be registered with the global registry: 147 | 148 | ```d 149 | static this() { 150 | /// bind the type to different matchers 151 | Registry.instance.register!(SysTime, SysTime)("between", &customOperation); 152 | Registry.instance.register!(SysTime, SysTime)("within", &customOperation); 153 | 154 | /// or use * to match any type 155 | Registry.instance.register("*", "*", "customOperation", &customOperation); 156 | } 157 | 158 | ``` 159 | 160 | ## Registering new serializers 161 | 162 | In order to setup an `Evaluation`, the actual and expected values need to be converted to a string. Most of the time, the default serializer will do a great job, but sometimes you might want to add a custom serializer for your types. 163 | 164 | ```d 165 | static this() { 166 | SerializerRegistry.instance.register(&jsonToString); 167 | } 168 | 169 | string jsonToString(Json value) { 170 | /// you can add here your custom serializer for Jsons 171 | } 172 | ``` 173 | 174 | 175 | # License 176 | 177 | MIT. See LICENSE for details. 178 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /api/above.md: -------------------------------------------------------------------------------- 1 | # The `above` operation 2 | 3 | [up](../README.md) 4 | 5 | Asserts that the tested value is greater than the tested value. However, it's often best to assert that the target is equal to its expected value. 6 | 7 | Works with: 8 | - expect(`core.time.Duration`).[to].[be].above(`core.time.Duration`) 9 | - expect(`std.datetime.systime.SysTime`).[to].[be].above(`std.datetime.systime.SysTime`) 10 | - expect(`byte`).[to].[be].above(`byte`) 11 | - expect(`ubyte`).[to].[be].above(`ubyte`) 12 | - expect(`short`).[to].[be].above(`short`) 13 | - expect(`ushort`).[to].[be].above(`ushort`) 14 | - expect(`int`).[to].[be].above(`int`) 15 | - expect(`uint`).[to].[be].above(`uint`) 16 | - expect(`long`).[to].[be].above(`long`) 17 | - expect(`ulong`).[to].[be].above(`ulong`) 18 | - expect(`float`).[to].[be].above(`float`) 19 | - expect(`double`).[to].[be].above(`double`) 20 | - expect(`real`).[to].[be].above(`real`) 21 | -------------------------------------------------------------------------------- /api/basic.md: -------------------------------------------------------------------------------- 1 | # Basic data types API 2 | 3 | [up](../README.md) 4 | 5 | Here are the examples of how you can use the `should` template with [basic data types](https://dlang.org/spec/type.html#basic-data-types). 6 | 7 | ## Summary 8 | 9 | - [Equal](#equal) 10 | - [Greater than](#greater-than) 11 | - [Above](#above) 12 | - [Less than](#less-than) 13 | - [Below](#below) 14 | - [Between](#between) 15 | - [Within](#within) 16 | - [Approximately](#approximately) 17 | 18 | ## Examples 19 | 20 | ### Equal 21 | 22 | Success expectations 23 | ```D 24 | 5.should.equal(5); 25 | 5.should.not.equal(6); 26 | 27 | true.should.equal(true); 28 | true.should.not.equal(false); 29 | 30 | /// or using the Assert utility 31 | Assert.equal(5, 5); 32 | Assert.notEqual(5, 6); 33 | ``` 34 | 35 | Failing expectations 36 | ```D 37 | 5.should.equal(6); 38 | 5.should.not.equal(5); 39 | 40 | true.should.equal(false); 41 | true.should.not.equal(true); 42 | 43 | /// or using the Assert utility 44 | Assert.equal(5, 6); 45 | Assert.notEqual(5, 5); 46 | ``` 47 | 48 | ### Greater than 49 | 50 | Success expectations 51 | ```D 52 | 5.should.be.greaterThan(4); 53 | 5.should.not.be.greaterThan(6); 54 | 55 | /// or using the Assert utility 56 | Assert.greaterThan(5, 4); 57 | Assert.notGreaterThan(5, 6); 58 | ``` 59 | 60 | Failing expectations 61 | ```D 62 | 5.should.be.greaterThan(5); 63 | 5.should.not.be.greaterThan(4); 64 | 65 | /// or using the Assert utility 66 | Assert.greaterThan(5, 5); 67 | Assert.notGreaterThan(5, 4); 68 | ``` 69 | 70 | ### Above 71 | 72 | Success expectations 73 | ```D 74 | 5.should.be.above(4); 75 | 5.should.not.be.above(6); 76 | 77 | /// or using the Assert utility 78 | Assert.above(5, 4); 79 | Assert.notAbove(5, 6); 80 | ``` 81 | 82 | Failing expectations 83 | ```D 84 | 5.should.be.above(5); 85 | 5.should.not.be.above(4); 86 | 87 | /// or using the Assert utility 88 | Assert.above(5, 5); 89 | Assert.notAbove(5, 4); 90 | ``` 91 | 92 | ### Less than 93 | 94 | Success expectations 95 | ```D 96 | 5.should.be.lessThan(6); 97 | 5.should.not.be.lessThan(4); 98 | 99 | /// or using the Assert utility 100 | Assert.lessThan(5, 6); 101 | Assert.notLessThan(5, 4); 102 | ``` 103 | 104 | Failing expectations 105 | ```D 106 | 5.should.be.lessThan(4); 107 | 5.should.not.be.lessThan(5); 108 | 109 | /// or using the Assert utility 110 | Assert.lessThan(5, 4); 111 | Assert.notLessThan(5, 5); 112 | ``` 113 | 114 | ### Below 115 | 116 | Success expectations 117 | ```D 118 | 5.should.be.below(6); 119 | 5.should.not.be.below(4); 120 | 121 | /// or using the Assert utility 122 | Assert.below(5, 6); 123 | Assert.notBelow(5, 4); 124 | ``` 125 | 126 | Failing expectations 127 | ```D 128 | 5.should.be.below(4); 129 | 5.should.not.be.below(5); 130 | 131 | /// or using the Assert utility 132 | Assert.below(5, 4); 133 | Assert.notBelow(5, 5); 134 | ``` 135 | 136 | ### Between 137 | 138 | Success expectations 139 | ```D 140 | 5.should.be.between(4, 6); 141 | 5.should.be.between(6, 4); 142 | 5.should.not.be.between(5, 6); 143 | 5.should.not.be.between(4, 5); 144 | 145 | /// or using the Assert utility 146 | Assert.between(5, 4, 6); 147 | Assert.notBetween(5, 5, 6); 148 | ``` 149 | 150 | Failing expectations 151 | ```D 152 | 5.should.be.between(5, 6); 153 | 5.should.be.between(4, 5); 154 | 5.should.not.be.between(4, 6); 155 | 5.should.not.be.between(6, 4); 156 | 157 | /// or using the Assert utility 158 | Assert.between(5, 4, 5); 159 | Assert.notBetween(5, 4, 6); 160 | ``` 161 | 162 | ### Within 163 | 164 | Success expectations 165 | ```D 166 | 5.should.be.within(4, 6); 167 | 5.should.be.within(6, 4); 168 | 5.should.not.be.within(5, 6); 169 | 5.should.not.be.within(4, 5); 170 | 171 | /// or using the Assert utility 172 | Assert.within(5, 4, 6); 173 | Assert.notWithin(5, 5, 6); 174 | ``` 175 | 176 | Failing expectations 177 | ```D 178 | 5.should.be.within(5, 6); 179 | 5.should.be.within(4, 5); 180 | 5.should.not.be.within(4, 6); 181 | 5.should.not.be.within(6, 4); 182 | 183 | /// or using the Assert utility 184 | Assert.within(5, 5, 6); 185 | Assert.notWithin(5, 4, 6); 186 | ``` 187 | 188 | ### Approximately 189 | 190 | Success expectations 191 | ```D 192 | (10f/3f).should.be.approximately(3, 0.34); 193 | (10f/3f).should.not.be.approximately(3, 0.24); 194 | 195 | /// or using the Assert utility 196 | Assert.approximately(10f/3f, 3, 0.34); 197 | Assert.notApproximately(10f/3f, 3, 0.24); 198 | ``` 199 | 200 | Failing expectations 201 | ```D 202 | (10f/3f).should.be.approximately(3, 0.3); 203 | (10f/3f).should.not.be.approximately(3, 0.34); 204 | 205 | /// or using the Assert utility 206 | Assert.approximately(10f/3f, 3, 0.3); 207 | Assert.notApproximately(10f/3f, 3, 0.34); 208 | ``` 209 | -------------------------------------------------------------------------------- /api/beNull.md: -------------------------------------------------------------------------------- 1 | # The `beNull` operation 2 | 3 | [up](../README.md) 4 | 5 | Asserts that the value is null. 6 | 7 | Works with: 8 | - expect(`*`).[to].[be].beNull(`*`) 9 | -------------------------------------------------------------------------------- /api/below.md: -------------------------------------------------------------------------------- 1 | # The `below` operation 2 | 3 | [up](../README.md) 4 | 5 | 6 | 7 | Works with: 8 | - expect(`core.time.Duration`).[to].[be].below(`core.time.Duration`) 9 | - expect(`std.datetime.systime.SysTime`).[to].[be].below(`std.datetime.systime.SysTime`) 10 | - expect(`byte`).[to].[be].below(`byte`) 11 | - expect(`ubyte`).[to].[be].below(`ubyte`) 12 | - expect(`short`).[to].[be].below(`short`) 13 | - expect(`ushort`).[to].[be].below(`ushort`) 14 | - expect(`int`).[to].[be].below(`int`) 15 | - expect(`uint`).[to].[be].below(`uint`) 16 | - expect(`long`).[to].[be].below(`long`) 17 | - expect(`ulong`).[to].[be].below(`ulong`) 18 | - expect(`float`).[to].[be].below(`float`) 19 | - expect(`double`).[to].[be].below(`double`) 20 | - expect(`real`).[to].[be].below(`real`) 21 | -------------------------------------------------------------------------------- /api/between.md: -------------------------------------------------------------------------------- 1 | # The `between` operation 2 | 3 | [up](../README.md) 4 | 5 | Asserts that the target is a number or a date greater than or equal to the given number or date start, and less than or equal to the given number or date finish respectively. However, it's often best to assert that the target is equal to its expected value. 6 | 7 | Works with: 8 | - expect(`core.time.Duration`).[to].[be].between(`core.time.Duration`) 9 | - expect(`std.datetime.systime.SysTime`).[to].[be].between(`std.datetime.systime.SysTime`) 10 | - expect(`byte`).[to].[be].between(`byte`) 11 | - expect(`ubyte`).[to].[be].between(`ubyte`) 12 | - expect(`short`).[to].[be].between(`short`) 13 | - expect(`ushort`).[to].[be].between(`ushort`) 14 | - expect(`int`).[to].[be].between(`int`) 15 | - expect(`uint`).[to].[be].between(`uint`) 16 | - expect(`long`).[to].[be].between(`long`) 17 | - expect(`ulong`).[to].[be].between(`ulong`) 18 | - expect(`float`).[to].[be].between(`float`) 19 | - expect(`double`).[to].[be].between(`double`) 20 | - expect(`real`).[to].[be].between(`real`) 21 | -------------------------------------------------------------------------------- /api/callable.md: -------------------------------------------------------------------------------- 1 | # Callable API 2 | 3 | [up](../README.md) 4 | 5 | Here are the examples of how you can use the `should` function with [exceptions](https://dlang.org/phobos/object.html#.Exception). 6 | 7 | ## Summary 8 | 9 | - [Throw exception](#throw-exception) 10 | - [Throw any exception](#throw-any-exception) 11 | - [Throw something](#throw-something) 12 | - [Execution time](#execution-time) 13 | - [Be null](#be-null) 14 | 15 | ## Examples 16 | 17 | ### Throw exception 18 | 19 | You can check if some code throws or not Exceptions. The `throwException` template will return a `ThrowableProxy` which for 20 | convience it has all the [Throwable](https://dlang.org/phobos/object.html#.Throwable) properties. A simple way of checking an 21 | exception message is: 22 | 23 | ```D 24 | ({ 25 | throw new CustomException("test"); 26 | }).should.throwException!CustomException.msg.should.equal("test"); 27 | ``` 28 | 29 | or 30 | 31 | ```D 32 | void myFunction() { 33 | throw new CustomException("test"); 34 | } 35 | 36 | /// This way of testing exceptions does not work with 37 | /// functions that return arrays or ranges 38 | myFunction.should.throwException!CustomException.msg.should.equal("test"); 39 | ``` 40 | 41 | The `ThrowableProxy` it also have a `withMessage` method that returns a new `should` structure, initialized with the exception message: 42 | 43 | ```D 44 | ({ 45 | throw new CustomException("test"); 46 | }).should.throwException!CustomException.withMessage.equal("test"); 47 | ``` 48 | 49 | or 50 | 51 | ```D 52 | void myFunction() { 53 | throw new CustomException("test"); 54 | } 55 | 56 | myFunction.should.throwException!CustomException.withMessage.equal("test"); 57 | ``` 58 | 59 | Success expectations 60 | ```D 61 | ({ 62 | throw new CustomException("test"); 63 | }).should.throwException!CustomException; 64 | 65 | ({ }).should.not.throwException!CustomException; 66 | ``` 67 | 68 | Failing expectations 69 | ```D 70 | ({ 71 | throw new Exception("test"); 72 | }).should.not.throwException!CustomException; 73 | 74 | ({ }).should.throwException!CustomException; 75 | ``` 76 | 77 | ### Throw any exception 78 | 79 | The `throwAnyException` assert is an alias for `throwException!Exception` 80 | 81 | Success expectations 82 | ```D 83 | ({ 84 | throw new Exception("test"); 85 | }).should.throwAnyException; 86 | 87 | ({ }).should.not.throwAnyException; 88 | ``` 89 | 90 | Failing expectations 91 | ```D 92 | ({ 93 | throw new Exception("test"); 94 | }).should.not.throwAnyException; 95 | 96 | ({ }).should.throwAnyException; 97 | ``` 98 | 99 | 100 | ### Throw something 101 | 102 | The `throwSomething` assert is an alias for `throwException!Throwable` 103 | 104 | ```D 105 | ({ 106 | assert(false, "test"); 107 | }).should.throwSomething.withMessage.equal("test"); 108 | ``` 109 | 110 | ### Execution time 111 | 112 | Success expectations 113 | ```D 114 | ({ }).should.haveExecutionTime.lessThan(1.msecs); 115 | ``` 116 | 117 | Failing expectations 118 | ```D 119 | ({ 120 | Thread.sleep(2.msecs); 121 | }).should.haveExecutionTime.lessThan(1.msecs); 122 | ``` 123 | 124 | ### Be null 125 | 126 | Success expectations 127 | ```D 128 | void delegate() action; 129 | action.should.beNull; 130 | 131 | ({ }).should.not.beNull; 132 | ``` 133 | 134 | Failing expectations 135 | ```D 136 | void delegate() action; 137 | action.should.not.beNull; 138 | 139 | ({ }).should.beNull; 140 | ``` 141 | -------------------------------------------------------------------------------- /api/endWith.md: -------------------------------------------------------------------------------- 1 | # The `endWith` operation 2 | 3 | [up](../README.md) 4 | 5 | 6 | 7 | Works with: 8 | - expect(`string`).[to].[be].endWith(`string`) 9 | - expect(`string`).[to].[be].endWith(`wstring`) 10 | - expect(`string`).[to].[be].endWith(`dstring`) 11 | - expect(`string`).[to].[be].endWith(`const(char)[]`) 12 | - expect(`wstring`).[to].[be].endWith(`string`) 13 | - expect(`wstring`).[to].[be].endWith(`wstring`) 14 | - expect(`wstring`).[to].[be].endWith(`dstring`) 15 | - expect(`wstring`).[to].[be].endWith(`const(char)[]`) 16 | - expect(`dstring`).[to].[be].endWith(`string`) 17 | - expect(`dstring`).[to].[be].endWith(`wstring`) 18 | - expect(`dstring`).[to].[be].endWith(`dstring`) 19 | - expect(`dstring`).[to].[be].endWith(`const(char)[]`) 20 | - expect(`const(char)[]`).[to].[be].endWith(`string`) 21 | - expect(`const(char)[]`).[to].[be].endWith(`wstring`) 22 | - expect(`const(char)[]`).[to].[be].endWith(`dstring`) 23 | - expect(`const(char)[]`).[to].[be].endWith(`const(char)[]`) 24 | - expect(`string`).[to].[be].endWith(`char`) 25 | - expect(`wstring`).[to].[be].endWith(`char`) 26 | - expect(`dstring`).[to].[be].endWith(`char`) 27 | - expect(`const(char)[]`).[to].[be].endWith(`char`) 28 | -------------------------------------------------------------------------------- /api/greaterOrEqualTo.md: -------------------------------------------------------------------------------- 1 | # The `greaterOrEqualTo` operation 2 | 3 | [up](../README.md) 4 | 5 | Asserts that the tested value is greater or equal than the tested value. However, it's often best to assert that the target is equal to its expected value. 6 | 7 | Works with: 8 | - expect(`core.time.Duration`).[to].[be].greaterOrEqualTo(`core.time.Duration`) 9 | - expect(`std.datetime.systime.SysTime`).[to].[be].greaterOrEqualTo(`std.datetime.systime.SysTime`) 10 | - expect(`byte`).[to].[be].greaterOrEqualTo(`byte`) 11 | - expect(`ubyte`).[to].[be].greaterOrEqualTo(`ubyte`) 12 | - expect(`short`).[to].[be].greaterOrEqualTo(`short`) 13 | - expect(`ushort`).[to].[be].greaterOrEqualTo(`ushort`) 14 | - expect(`int`).[to].[be].greaterOrEqualTo(`int`) 15 | - expect(`uint`).[to].[be].greaterOrEqualTo(`uint`) 16 | - expect(`long`).[to].[be].greaterOrEqualTo(`long`) 17 | - expect(`ulong`).[to].[be].greaterOrEqualTo(`ulong`) 18 | - expect(`float`).[to].[be].greaterOrEqualTo(`float`) 19 | - expect(`double`).[to].[be].greaterOrEqualTo(`double`) 20 | - expect(`real`).[to].[be].greaterOrEqualTo(`real`) 21 | - expect(`byte`).[to].[be].greaterOrEqualTo(`int`) 22 | - expect(`ubyte`).[to].[be].greaterOrEqualTo(`int`) 23 | - expect(`short`).[to].[be].greaterOrEqualTo(`int`) 24 | - expect(`ushort`).[to].[be].greaterOrEqualTo(`int`) 25 | - expect(`int`).[to].[be].greaterOrEqualTo(`int`) 26 | - expect(`uint`).[to].[be].greaterOrEqualTo(`int`) 27 | - expect(`long`).[to].[be].greaterOrEqualTo(`int`) 28 | - expect(`ulong`).[to].[be].greaterOrEqualTo(`int`) 29 | - expect(`float`).[to].[be].greaterOrEqualTo(`int`) 30 | - expect(`double`).[to].[be].greaterOrEqualTo(`int`) 31 | - expect(`real`).[to].[be].greaterOrEqualTo(`int`) 32 | -------------------------------------------------------------------------------- /api/greaterThan.md: -------------------------------------------------------------------------------- 1 | # The `greaterThan` operation 2 | 3 | [up](../README.md) 4 | 5 | Asserts that the tested value is greater than the tested value. However, it's often best to assert that the target is equal to its expected value. 6 | 7 | Works with: 8 | - expect(`core.time.Duration`).[to].[be].greaterThan(`core.time.Duration`) 9 | - expect(`std.datetime.systime.SysTime`).[to].[be].greaterThan(`std.datetime.systime.SysTime`) 10 | - expect(`byte`).[to].[be].greaterThan(`byte`) 11 | - expect(`ubyte`).[to].[be].greaterThan(`ubyte`) 12 | - expect(`short`).[to].[be].greaterThan(`short`) 13 | - expect(`ushort`).[to].[be].greaterThan(`ushort`) 14 | - expect(`int`).[to].[be].greaterThan(`int`) 15 | - expect(`uint`).[to].[be].greaterThan(`uint`) 16 | - expect(`long`).[to].[be].greaterThan(`long`) 17 | - expect(`ulong`).[to].[be].greaterThan(`ulong`) 18 | - expect(`float`).[to].[be].greaterThan(`float`) 19 | - expect(`double`).[to].[be].greaterThan(`double`) 20 | - expect(`real`).[to].[be].greaterThan(`real`) 21 | - expect(`byte`).[to].[be].greaterThan(`int`) 22 | - expect(`ubyte`).[to].[be].greaterThan(`int`) 23 | - expect(`short`).[to].[be].greaterThan(`int`) 24 | - expect(`ushort`).[to].[be].greaterThan(`int`) 25 | - expect(`int`).[to].[be].greaterThan(`int`) 26 | - expect(`uint`).[to].[be].greaterThan(`int`) 27 | - expect(`long`).[to].[be].greaterThan(`int`) 28 | - expect(`ulong`).[to].[be].greaterThan(`int`) 29 | - expect(`float`).[to].[be].greaterThan(`int`) 30 | - expect(`double`).[to].[be].greaterThan(`int`) 31 | - expect(`real`).[to].[be].greaterThan(`int`) 32 | -------------------------------------------------------------------------------- /api/instanceOf.md: -------------------------------------------------------------------------------- 1 | # The `instanceOf` operation 2 | 3 | [up](../README.md) 4 | 5 | 6 | 7 | Works with: 8 | - expect(`*`).[to].[be].instanceOf(`*`) 9 | -------------------------------------------------------------------------------- /api/lessOrEqualTo.md: -------------------------------------------------------------------------------- 1 | # The `lessOrEqualTo` operation 2 | 3 | [up](../README.md) 4 | 5 | 6 | 7 | Works with: 8 | - expect(`byte`).[to].[be].lessOrEqualTo(`byte`) 9 | - expect(`ubyte`).[to].[be].lessOrEqualTo(`ubyte`) 10 | - expect(`short`).[to].[be].lessOrEqualTo(`short`) 11 | - expect(`ushort`).[to].[be].lessOrEqualTo(`ushort`) 12 | - expect(`int`).[to].[be].lessOrEqualTo(`int`) 13 | - expect(`uint`).[to].[be].lessOrEqualTo(`uint`) 14 | - expect(`long`).[to].[be].lessOrEqualTo(`long`) 15 | - expect(`ulong`).[to].[be].lessOrEqualTo(`ulong`) 16 | - expect(`float`).[to].[be].lessOrEqualTo(`float`) 17 | - expect(`double`).[to].[be].lessOrEqualTo(`double`) 18 | - expect(`real`).[to].[be].lessOrEqualTo(`real`) 19 | - expect(`byte`).[to].[be].lessOrEqualTo(`int`) 20 | - expect(`ubyte`).[to].[be].lessOrEqualTo(`int`) 21 | - expect(`short`).[to].[be].lessOrEqualTo(`int`) 22 | - expect(`ushort`).[to].[be].lessOrEqualTo(`int`) 23 | - expect(`int`).[to].[be].lessOrEqualTo(`int`) 24 | - expect(`uint`).[to].[be].lessOrEqualTo(`int`) 25 | - expect(`long`).[to].[be].lessOrEqualTo(`int`) 26 | - expect(`ulong`).[to].[be].lessOrEqualTo(`int`) 27 | - expect(`float`).[to].[be].lessOrEqualTo(`int`) 28 | - expect(`double`).[to].[be].lessOrEqualTo(`int`) 29 | - expect(`real`).[to].[be].lessOrEqualTo(`int`) 30 | -------------------------------------------------------------------------------- /api/lessThan.md: -------------------------------------------------------------------------------- 1 | # The `lessThan` operation 2 | 3 | [up](../README.md) 4 | 5 | Asserts that the tested value is less than the tested value. However, it's often best to assert that the target is equal to its expected value. 6 | 7 | Works with: 8 | - expect(`core.time.Duration`).[to].[be].lessThan(`core.time.Duration`) 9 | - expect(`std.datetime.systime.SysTime`).[to].[be].lessThan(`std.datetime.systime.SysTime`) 10 | - expect(`byte`).[to].[be].lessThan(`byte`) 11 | - expect(`ubyte`).[to].[be].lessThan(`ubyte`) 12 | - expect(`short`).[to].[be].lessThan(`short`) 13 | - expect(`ushort`).[to].[be].lessThan(`ushort`) 14 | - expect(`int`).[to].[be].lessThan(`int`) 15 | - expect(`uint`).[to].[be].lessThan(`uint`) 16 | - expect(`long`).[to].[be].lessThan(`long`) 17 | - expect(`ulong`).[to].[be].lessThan(`ulong`) 18 | - expect(`float`).[to].[be].lessThan(`float`) 19 | - expect(`double`).[to].[be].lessThan(`double`) 20 | - expect(`real`).[to].[be].lessThan(`real`) 21 | - expect(`byte`).[to].[be].lessThan(`int`) 22 | - expect(`ubyte`).[to].[be].lessThan(`int`) 23 | - expect(`short`).[to].[be].lessThan(`int`) 24 | - expect(`ushort`).[to].[be].lessThan(`int`) 25 | - expect(`int`).[to].[be].lessThan(`int`) 26 | - expect(`uint`).[to].[be].lessThan(`int`) 27 | - expect(`long`).[to].[be].lessThan(`int`) 28 | - expect(`ulong`).[to].[be].lessThan(`int`) 29 | - expect(`float`).[to].[be].lessThan(`int`) 30 | - expect(`double`).[to].[be].lessThan(`int`) 31 | - expect(`real`).[to].[be].lessThan(`int`) 32 | -------------------------------------------------------------------------------- /api/objects.md: -------------------------------------------------------------------------------- 1 | # Objects API 2 | 3 | [up](../README.md) 4 | 5 | Here are the examples of how you can use the `should` template with [objects](http://dlang.org/spec/class.html). 6 | 7 | ## Summary 8 | 9 | - [Be null](#be-null) 10 | - [Instance of](#instance-of) 11 | - [Equal](#equal) 12 | 13 | ## Examples 14 | 15 | ### Be null 16 | 17 | Success expectations 18 | ```D 19 | Object o = null; 20 | 21 | o.should.beNull; 22 | (new Object).should.not.beNull; 23 | 24 | /// or using the Assert utility 25 | Assert.beNull(o); 26 | Assert.notNull(new Object); 27 | ``` 28 | 29 | Failing expectations 30 | ```D 31 | Object o = null; 32 | 33 | o.should.not.beNull; 34 | (new Object).should.beNull; 35 | ``` 36 | 37 | 38 | ### Instance of 39 | 40 | ```D 41 | class BaseClass { } 42 | class ExtendedClass : BaseClass { } 43 | class SomeClass { } 44 | class OtherClass { } 45 | 46 | auto someObject = new SomeClass; 47 | auto otherObject = new OtherClass; 48 | auto extendedObject = new ExtendedClass; 49 | ``` 50 | 51 | Success expectations 52 | ```D 53 | someObject.should.be.instanceOf!SomeClass; 54 | extendedObject.should.be.instanceOf!BaseClass; 55 | 56 | someObject.should.not.be.instanceOf!OtherClass; 57 | someObject.should.not.be.instanceOf!BaseClass; 58 | ``` 59 | 60 | Failing expectations 61 | ```D 62 | otherObject.should.be.instanceOf!SomeClass; 63 | otherObject.should.not.be.instanceOf!OtherClass; 64 | ``` 65 | 66 | ### Equal 67 | 68 | ```D 69 | class TestEqual { 70 | private int value; 71 | 72 | this(int value) { 73 | this.value = value; 74 | } 75 | } 76 | 77 | auto instance = new TestEqual(1); 78 | ``` 79 | 80 | Success expectations 81 | ```D 82 | instance.should.equal(instance); 83 | instance.should.not.equal(new TestEqual(1)); 84 | ``` 85 | 86 | Failing expectations 87 | ```D 88 | instance.should.not.equal(instance); 89 | instance.should.equal(new TestEqual(1)); 90 | ``` 91 | -------------------------------------------------------------------------------- /api/ranges.md: -------------------------------------------------------------------------------- 1 | # Arrays API 2 | 3 | [up](../README.md) 4 | 5 | Here are the examples of how you can use the `should` template with [ranges](http://dlang.org/phobos/std_range.html) and [arrays](https://dlang.org/spec/arrays.html). 6 | 7 | ## Summary 8 | 9 | - [Equal](#equal) 10 | - [Approximately](#approximately) 11 | - [Contain](#contain) 12 | - [ContainOnly](#containOnly) 13 | 14 | ## Examples 15 | 16 | ### Equal 17 | 18 | Success expectations 19 | ```D 20 | [1, 2, 3].should.equal([1, 2, 3]); 21 | [1, 2, 3].should.not.equal([2, 1, 3]); 22 | 23 | /// or using the Assert utility 24 | Assert.equal([1, 2, 3], [1, 2, 3]); 25 | Assert.notEqual([1, 2, 3], [2, 1, 3]); 26 | ``` 27 | 28 | Failing expectations 29 | ```D 30 | [1, 2, 3].should.equal([4, 5]); 31 | [1, 2, 3].should.equal([2, 3, 1]); 32 | [1, 2, 3].should.not.equal([1, 2, 3]); 33 | 34 | /// or using the Assert utility 35 | Assert.equal([1, 2, 3], [1, 3, 1]); 36 | Assert.notEqual([1, 2, 3], [1, 2, 3]); 37 | ``` 38 | 39 | ### Approximately 40 | 41 | Success expectations 42 | ```D 43 | [0.350, 0.501, 0.341].should.be.approximately([0.35, 0.50, 0.34], 0.01); 44 | 45 | [0.350, 0.501, 0.341].should.not.be.approximately([0.35, 0.50, 0.34], 0.00001); 46 | [0.350, 0.501, 0.341].should.not.be.approximately([0.501, 0.350, 0.341], 0.001); 47 | [0.350, 0.501, 0.341].should.not.be.approximately([0.350, 0.501], 0.001); 48 | 49 | /// or using the Assert utility 50 | Assert.approximately([0.350, 0.501, 0.341], [0.35, 0.50, 0.34], 0.01); 51 | Assert.notApproximately([0.350, 0.501, 0.341], [0.350, 0.501], 0.01); 52 | ``` 53 | 54 | Failing expectations 55 | ```D 56 | [0.350, 0.501, 0.341].should.be.approximately([0.35, 0.50, 0.34], 0.0001); 57 | 58 | /// or using the Assert utility 59 | Assert.approximately([0.350, 0.501, 0.341], [0.35, 0.50, 0.34], 0.0001); 60 | ``` 61 | 62 | ### Contain 63 | 64 | Success expectations 65 | ```D 66 | [1, 2, 3].should.contain([2, 1]); 67 | [1, 2, 3].should.not.contain([4, 5]); 68 | 69 | [1, 2, 3].should.contain(1); 70 | [1, 2, 3].should.not.contain(5); 71 | 72 | /// or using the Assert utility 73 | Assert.contain([1, 2, 3], [2, 1]); 74 | Assert.notContain([1, 2, 3], [3, 4]); 75 | 76 | Assert.contain([1, 2, 3], 1); 77 | Assert.notContain([1, 2, 3], 4); 78 | ``` 79 | 80 | Failing expectations 81 | ```D 82 | [1, 2, 3].should.contain([4, 5]); 83 | [1, 2, 3].should.not.contain([1, 2]); 84 | [1, 2, 3].should.not.contain([3, 4]); 85 | 86 | [1, 2, 3].should.contain(4); 87 | [1, 2, 3].should.not.contain(2); 88 | ``` 89 | 90 | ### Contain only 91 | 92 | Success expectations 93 | ```D 94 | [1, 2, 3].should.containOnly([3, 2, 1]); 95 | [1, 2, 3].should.not.containOnly([2, 1]); 96 | 97 | [1, 2, 2].should.containOnly([2, 1, 2]); 98 | [1, 2, 2].should.not.containOnly([2, 1]); 99 | 100 | [2, 2].should.containOnly([2, 2]); 101 | [2, 2, 2].should.not.containOnly([2, 2]); 102 | 103 | /// or using the Assert utility 104 | Assert.containOnly([1, 2, 3], [3, 2, 1]); 105 | Assert.notContainOnly([1, 2, 3], [2, 1]); 106 | ``` 107 | 108 | Failing expectations 109 | ```D 110 | [1, 2, 3].should.containOnly([2, 1]); 111 | [1, 2].should.not.containOnly([2, 1]); 112 | [2, 2].should.containOnly([2]); 113 | [3, 3].should.containOnly([2]); 114 | [2, 2].should.not.containOnly([2, 2]); 115 | ``` 116 | -------------------------------------------------------------------------------- /api/startWith.md: -------------------------------------------------------------------------------- 1 | # The `startWith` operation 2 | 3 | [up](../README.md) 4 | 5 | 6 | 7 | Works with: 8 | - expect(`string`).[to].[be].startWith(`string`) 9 | - expect(`string`).[to].[be].startWith(`wstring`) 10 | - expect(`string`).[to].[be].startWith(`dstring`) 11 | - expect(`string`).[to].[be].startWith(`const(char)[]`) 12 | - expect(`wstring`).[to].[be].startWith(`string`) 13 | - expect(`wstring`).[to].[be].startWith(`wstring`) 14 | - expect(`wstring`).[to].[be].startWith(`dstring`) 15 | - expect(`wstring`).[to].[be].startWith(`const(char)[]`) 16 | - expect(`dstring`).[to].[be].startWith(`string`) 17 | - expect(`dstring`).[to].[be].startWith(`wstring`) 18 | - expect(`dstring`).[to].[be].startWith(`dstring`) 19 | - expect(`dstring`).[to].[be].startWith(`const(char)[]`) 20 | - expect(`const(char)[]`).[to].[be].startWith(`string`) 21 | - expect(`const(char)[]`).[to].[be].startWith(`wstring`) 22 | - expect(`const(char)[]`).[to].[be].startWith(`dstring`) 23 | - expect(`const(char)[]`).[to].[be].startWith(`const(char)[]`) 24 | - expect(`string`).[to].[be].startWith(`char`) 25 | - expect(`wstring`).[to].[be].startWith(`char`) 26 | - expect(`dstring`).[to].[be].startWith(`char`) 27 | - expect(`const(char)[]`).[to].[be].startWith(`char`) 28 | -------------------------------------------------------------------------------- /api/strings.md: -------------------------------------------------------------------------------- 1 | # Strings API 2 | 3 | [up](../README.md) 4 | 5 | Here are the examples of how you can use the `should` template with [strings](https://dlang.org/spec/arrays.html#strings). 6 | 7 | ## Summary 8 | 9 | - [Equal](#equal) 10 | - [Contain](#contain) 11 | - [Start with](#start-with) 12 | - [End with](#end-with) 13 | 14 | ## Examples 15 | 16 | ### Equal 17 | 18 | Success expectations 19 | ```D 20 | "test string".should.equal("test string"); 21 | "test string".should.not.equal("test"); 22 | 23 | /// or using the Assert utility 24 | Assert.equal("test string", "test string"); 25 | Assert.notEqual("test string", "test"); 26 | ``` 27 | 28 | Failing expectations 29 | ```D 30 | "test string".should.equal("test"); 31 | "test string".should.not.equal("test string"); 32 | ``` 33 | 34 | ### Contain 35 | 36 | Success expectations 37 | ```D 38 | "test string".should.contain(["string", "test"]); 39 | "test string".should.not.contain(["other", "value"]); 40 | 41 | "test string".should.contain("string"); 42 | "test string".should.not.contain("other"); 43 | 44 | "test string".should.contain('s'); 45 | "test string".should.not.contain('z'); 46 | 47 | /// or using the Assert utility 48 | Assert.contain("test string", ["string", "test"]); 49 | Assert.notContain("test string", ["other", "value"]); 50 | 51 | Assert.contain("test string", "test"); 52 | Assert.notContain("test string", "other"); 53 | 54 | Assert.contain("test string", 't'); 55 | Assert.notContain("test string", 'z'); 56 | ``` 57 | 58 | Failing expectations 59 | ```D 60 | "test string".should.contain(["other", "message"]); 61 | "test string".should.contain("other"); 62 | "test string".should.contain('o'); 63 | ``` 64 | 65 | ### Start with 66 | 67 | Success expectations 68 | ```D 69 | "test string".should.startWith("test"); 70 | "test string".should.not.startWith("other"); 71 | 72 | "test string".should.startWith('t'); 73 | "test string".should.not.startWith('o'); 74 | 75 | /// or using the Assert utility 76 | Assert.startWith(test string", "test"); 77 | Assert.notStartWith("test string", "other"); 78 | 79 | Assert.startWith("test string", 't'); 80 | Assert.notStartWith(test string", 'o'); 81 | ``` 82 | 83 | Failing expectations 84 | ```D 85 | "test string".should.startWith("other"); 86 | "test string".should.not.startWith("test"); 87 | 88 | "test string".should.startWith('o'); 89 | "test string".should.not.startWith('t'); 90 | ``` 91 | 92 | ### End with 93 | 94 | Success expectations 95 | ```D 96 | "test string".should.endWith("string"); 97 | "test string".should.not.endWith("other"); 98 | 99 | "test string".should.endWith('g'); 100 | "test string".should.not.endWith('w'); 101 | 102 | /// or using the Assert utility 103 | Assert.endWith(test string", "string"); 104 | Assert.notEndWith("test string", "other"); 105 | 106 | Assert.endWith("test string", 'g'); 107 | Assert.notEndWith(test string", 'o'); 108 | ``` 109 | 110 | Failing expectations 111 | ```D 112 | "test string".should.endWith("other"); 113 | "test string".should.not.endWith("string"); 114 | 115 | "test string".should.endWith('t'); 116 | "test string".should.not.endWith('g'); 117 | ``` 118 | -------------------------------------------------------------------------------- /api/throwAnyException.md: -------------------------------------------------------------------------------- 1 | # The `throwAnyException` operation 2 | 3 | [up](../README.md) 4 | 5 | 6 | 7 | Works with: 8 | - expect(`callable`).[to].[be].throwAnyException(``) 9 | - expect(`*`).[to].[be].throwAnyException(`*`) 10 | -------------------------------------------------------------------------------- /api/throwException.md: -------------------------------------------------------------------------------- 1 | # The `throwException` operation 2 | 3 | [up](../README.md) 4 | 5 | 6 | 7 | Works with: 8 | - expect(`callable`).[to].[be].throwException(``) 9 | - expect(`*`).[to].[be].throwException(`*`) 10 | -------------------------------------------------------------------------------- /api/throwSomething.md: -------------------------------------------------------------------------------- 1 | # The `throwSomething` operation 2 | 3 | [up](../README.md) 4 | 5 | 6 | 7 | Works with: 8 | - expect(`*`).[to].[be].throwSomething(`*`) 9 | -------------------------------------------------------------------------------- /api/vibe-json.md: -------------------------------------------------------------------------------- 1 | # Vibe.d Json API 2 | 3 | [up](../README.md) 4 | 5 | Theese are some utilities that helps you to write easier asserts for the [Json](https://vibed.org/api/vibe.data.json/) data type provided by the [vibe.d](https://vibed.org/) library. 6 | 7 | ## Setup 8 | 9 | 1. Include the vibe assert package package: `fluent-asserts-vibe` 10 | 2. Import the module: `import fluentasserts.vibe.json` or `import fluent.asserts` 11 | 12 | ## Summary 13 | 14 | - [Keys](#keys) 15 | 16 | ## Examples 17 | 18 | ### Keys 19 | 20 | `string[] keys(Json obj, const string file = __FILE__, const size_t line = __LINE__)` 21 | 22 | Returns an array that contains the keys of an Json object. 23 | 24 | Success expectations 25 | ```D 26 | Json.emptyObject.keys.length.should.equal(0); 27 | ``` 28 | 29 | ```D 30 | auto obj = Json.emptyObject; 31 | obj["key1"] = 1; 32 | obj["key2"] = 3; 33 | 34 | obj.keys.length.should.equal(2); 35 | obj.keys.should.contain(["key1", "key2"]); 36 | ``` 37 | 38 | Failing expectations 39 | ```D 40 | Json.emptyArray.keys.should.contain(["key1", "key2"]); 41 | // fails with: The json should be an object. `array` found. 42 | ``` 43 | -------------------------------------------------------------------------------- /api/vibe-requests.md: -------------------------------------------------------------------------------- 1 | # Vibe.d Requests API 2 | 3 | [up](../README.md) 4 | 5 | Mocking HTTP requests are usefull for api tests. This module allows you to mock requests for a [vibe.d](https://vibed.org/) router. 6 | 7 | ## Setup 8 | 9 | 1. Include the vibe assert package package: `fluent-asserts-vibe` 10 | 2. Import the module: `import fluentasserts.vibe.request` or `import fluent.asserts` 11 | 12 | ## Summary 13 | 14 | - [Mocking GET requests](#mocking-get-requests) 15 | - [Other requests](#other-requests) 16 | - [Sending headers](#sending-headers) 17 | - [Sending string data](#sending-string-data) 18 | - [Sending form data](#sending-form-data) 19 | - [Sending JSON data](#sending-json-data) 20 | - [Receive JSON data](#receive-json-data) 21 | - [Expect status code](#expect-status-code) 22 | - [Expect header value](#expect-header-value) 23 | 24 | ## Examples 25 | 26 | ### Mocking GET requests 27 | 28 | Returns an array containg the keys of an Json object. 29 | 30 | Given a simple router 31 | ```D 32 | auto router = new URLRouter(); 33 | 34 | void sayHello(HTTPServerRequest req, HTTPServerResponse res) 35 | { 36 | res.writeBody("hello"); 37 | } 38 | 39 | router.any("*", &sayHello); 40 | ``` 41 | 42 | You can mock requests like this: 43 | ```D 44 | request(router) 45 | .get("/") 46 | .end((Response response) => { 47 | response.bodyString.should.equal("hello"); 48 | }); 49 | ``` 50 | 51 | The above example creates a `GET` requests and sends it to the router. The handler response is sent as a 52 | callback to the `end` callback, where you can add your custom asserts. 53 | 54 | ### Other requests 55 | 56 | You can also mock `POST`, `PATCH`, `PUT`, `DELETE` requests by using the folowing methods: 57 | 58 | ```D 59 | RequestRouter post(string path); 60 | RequestRouter patch(string path); 61 | RequestRouter put(string path); 62 | RequestRouter delete_(string path); 63 | ``` 64 | 65 | Or if you want to pass a different (HTTP method)[https://vibed.org/api/vibe.http.common/HTTPMethod] you can use the generic request methods: 66 | ```D 67 | customMethod(HTTPMethod method)(string path); 68 | customMethod(HTTPMethod method)(URL url); 69 | ``` 70 | 71 | ### Sending headers 72 | 73 | ```D 74 | auto router = new URLRouter(); 75 | 76 | void checkHeaders(HTTPServerRequest req, HTTPServerResponse) 77 | { 78 | req.headers["Accept"].should.equal("application/json"); 79 | } 80 | 81 | router.any("*", &checkHeaders); 82 | 83 | request(router) 84 | .get("/") 85 | .header("Accept", "application/json") 86 | .end(); 87 | ``` 88 | 89 | ### Sending string data 90 | 91 | ```D 92 | import std.string; 93 | 94 | auto router = new URLRouter(); 95 | 96 | void checkStringData(HTTPServerRequest req, HTTPServerResponse) 97 | { 98 | req.bodyReader.peek.assumeUTF.should.equal("raw string"); 99 | } 100 | 101 | router.any("*", &checkStringData); 102 | ``` 103 | 104 | ```D 105 | request(router) 106 | .post("/") 107 | .send("raw string") 108 | .end(); 109 | ``` 110 | 111 | 112 | ### Sending form data 113 | 114 | ```D 115 | auto router = new URLRouter(); 116 | 117 | void checkFormData(HTTPServerRequest req, HTTPServerResponse) 118 | { 119 | req.headers["content-type"].should.equal("application/x-www-form-urlencoded"); 120 | req.form["key1"].should.equal("value1"); 121 | req.form["key2"].should.equal("val2ue2"); 122 | } 123 | 124 | router.any("*", &checkFormData); 125 | ``` 126 | 127 | ```D 128 | request(router) 129 | .post("/") 130 | .send(["key1": "value1", "key2": "value2"]) 131 | .end(); 132 | ``` 133 | 134 | ### Sending JSON data 135 | 136 | ```D 137 | auto router = new URLRouter(); 138 | 139 | void checkJsonData(HTTPServerRequest req, HTTPServerResponse) 140 | { 141 | req.json["key"].to!string.should.equal("value"); 142 | } 143 | 144 | router.any("*", &checkJsonData); 145 | ``` 146 | 147 | ```D 148 | request(router) 149 | .post("/") 150 | .send(`{ "key": "value" }`.parseJsonString) 151 | .end(); 152 | ``` 153 | 154 | 155 | ### Receive JSON data 156 | 157 | ```D 158 | auto router = new URLRouter(); 159 | 160 | void respondJsonData(HTTPServerRequest, HTTPServerResponse res) 161 | { 162 | res.writeJsonBody(`{ "key": "value"}`.parseJsonString); 163 | } 164 | 165 | router.any("*", &respondJsonData); 166 | ``` 167 | 168 | ```D 169 | request(router) 170 | .get("/") 171 | .end((Response response) => { 172 | response.bodyJson["key"].to!string.should.equal("value"); 173 | }); 174 | ``` 175 | 176 | ### Expect status code 177 | 178 | ```D 179 | auto router = new URLRouter(); 180 | 181 | void respondStatus(HTTPServerRequest, HTTPServerResponse res) 182 | { 183 | res.statusCode = 200; 184 | res.writeBody(""); 185 | } 186 | 187 | router.get("*", &respondStatus); 188 | ``` 189 | 190 | ```D 191 | request(router) 192 | .get("/") 193 | .expectStatusCode(200) 194 | .end(); 195 | 196 | 197 | should.throwAnyException({ 198 | request(router) 199 | .post("/") 200 | .expectStatusCode(200) 201 | .end(); 202 | }).msg.should.equal("Expected status code `200` not found. Got `404` instead"); 203 | ``` 204 | 205 | 206 | ### Expect header value 207 | 208 | ```D 209 | auto router = new URLRouter(); 210 | 211 | void respondHeader(HTTPServerRequest, HTTPServerResponse res) 212 | { 213 | res.headers["some-header"] = "some-value"; 214 | res.writeBody(""); 215 | } 216 | 217 | router.get("*", &respondHeader); 218 | ``` 219 | 220 | Check for the exact header value: 221 | ```D 222 | request(router) 223 | .get("/") 224 | .expectHeader("some-header", "some-value") 225 | .end(); 226 | 227 | should.throwAnyException({ 228 | request(router) 229 | .get("/") 230 | .expectHeader("some-header", "other-value") 231 | .end(); 232 | }).msg.should.contain("Response header `some-header` has an unexpected value"); 233 | 234 | should.throwAnyException({ 235 | request(router) 236 | .post("/") 237 | .expectHeader("some-header", "some-value") 238 | .end(); 239 | }).msg.should.equal("Response header `some-header` is missing."); 240 | ``` 241 | 242 | Check if a header exists 243 | 244 | ```D 245 | request(router) 246 | .get("/") 247 | .expectHeaderExist("some-header") 248 | .end(); 249 | 250 | 251 | should.throwAnyException({ 252 | request(router) 253 | .post("/") 254 | .expectHeaderExist("some-header") 255 | .end(); 256 | }).msg.should.equal("Response header `some-header` is missing."); 257 | ``` 258 | 259 | Check if a header contains a string 260 | ```D 261 | request(router) 262 | .get("/") 263 | .expectHeaderContains("some-header", "value") 264 | .end(); 265 | 266 | 267 | should.throwAnyException({ 268 | request(router) 269 | .get("/") 270 | .expectHeaderContains("some-header", "other") 271 | .end(); 272 | }).msg.should.contain("Response header `some-header` has an unexpected value."); 273 | ``` 274 | -------------------------------------------------------------------------------- /api/within.md: -------------------------------------------------------------------------------- 1 | # The `within` operation 2 | 3 | [up](../README.md) 4 | 5 | Asserts that the target is a number or a date greater than or equal to the given number or date start, and less than or equal to the given number or date finish respectively. However, it's often best to assert that the target is equal to its expected value. 6 | 7 | Works with: 8 | - expect(`core.time.Duration`).[to].[be].within(`core.time.Duration`) 9 | - expect(`std.datetime.systime.SysTime`).[to].[be].within(`std.datetime.systime.SysTime`) 10 | - expect(`byte`).[to].[be].within(`byte`) 11 | - expect(`ubyte`).[to].[be].within(`ubyte`) 12 | - expect(`short`).[to].[be].within(`short`) 13 | - expect(`ushort`).[to].[be].within(`ushort`) 14 | - expect(`int`).[to].[be].within(`int`) 15 | - expect(`uint`).[to].[be].within(`uint`) 16 | - expect(`long`).[to].[be].within(`long`) 17 | - expect(`ulong`).[to].[be].within(`ulong`) 18 | - expect(`float`).[to].[be].within(`float`) 19 | - expect(`double`).[to].[be].within(`double`) 20 | - expect(`real`).[to].[be].within(`real`) 21 | -------------------------------------------------------------------------------- /dscanner.ini: -------------------------------------------------------------------------------- 1 | ; Configure which static analysis checks are enabled 2 | [analysis.config.StaticAnalysisConfig] 3 | ; Check variable, class, struct, interface, union, and function names against t 4 | ; he Phobos style guide 5 | style_check="enabled" 6 | ; Check for array literals that cause unnecessary allocation 7 | enum_array_literal_check="enabled" 8 | ; Check for poor exception handling practices 9 | exception_check="enabled" 10 | ; Check for use of the deprecated 'delete' keyword 11 | delete_check="enabled" 12 | ; Check for use of the deprecated floating point operators 13 | float_operator_check="enabled" 14 | ; Check number literals for readability 15 | number_style_check="enabled" 16 | ; Checks that opEquals, opCmp, toHash, and toString are either const, immutable 17 | ; , or inout. 18 | object_const_check="enabled" 19 | ; Checks for .. expressions where the left side is larger than the right. 20 | backwards_range_check="enabled" 21 | ; Checks for if statements whose 'then' block is the same as the 'else' block 22 | if_else_same_check="enabled" 23 | ; Checks for some problems with constructors 24 | constructor_check="enabled" 25 | ; Checks for unused variables 26 | unused_variable_check="enabled" 27 | ; Checks for unused labels 28 | unused_label_check="enabled" 29 | ; Checks for unused function parameters 30 | unused_parameter_check="enabled" 31 | ; Checks for duplicate attributes 32 | duplicate_attribute="enabled" 33 | ; Checks that opEquals and toHash are both defined or neither are defined 34 | opequals_tohash_check="enabled" 35 | ; Checks for subtraction from .length properties 36 | length_subtraction_check="enabled" 37 | ; Checks for methods or properties whose names conflict with built-in propertie 38 | ; s 39 | builtin_property_names_check="enabled" 40 | ; Checks for confusing code in inline asm statements 41 | asm_style_check="enabled" 42 | ; Checks for confusing logical operator precedence 43 | logical_precedence_check="enabled" 44 | ; Checks for undocumented public declarations 45 | undocumented_declaration_check="enabled" 46 | ; Checks for poor placement of function attributes 47 | function_attribute_check="enabled" 48 | ; Checks for use of the comma operator 49 | comma_expression_check="enabled" 50 | ; Checks for local imports that are too broad 51 | local_import_check="enabled" 52 | ; Checks for variables that could be declared immutable 53 | could_be_immutable_check="enabled" 54 | ; Checks for redundant expressions in if statements 55 | redundant_if_check="enabled" 56 | ; Checks for redundant parenthesis 57 | redundant_parens_check="enabled" 58 | ; Checks for mismatched argument and parameter names 59 | mismatched_args_check="enabled" 60 | ; Checks for labels with the same name as variables 61 | label_var_same_name_check="enabled" 62 | ; Checks for lines longer than 120 characters 63 | long_line_check="disabled" 64 | ; Checks for assignment to auto-ref function parameters 65 | auto_ref_assignment_check="enabled" 66 | ; Checks for incorrect infinite range definitions 67 | incorrect_infinite_range_check="enabled" 68 | ; Checks for asserts that are always true 69 | useless_assert_check="enabled" 70 | ; Check for uses of the old-style alias syntax 71 | alias_syntax_check="enabled" 72 | ; Checks for else if that should be else static if 73 | static_if_else_check="enabled" 74 | ; Check for unclear lambda syntax 75 | lambda_return_check="enabled" 76 | ; Check for auto function without return statement 77 | auto_function_check="enabled" 78 | ; Check for sortedness of imports 79 | imports_sortedness="disabled" 80 | ; Check for explicitly annotated unittests 81 | explicitly_annotated_unittests="disabled" 82 | ; Check for properly documented public functions (Returns, Params) 83 | properly_documented_public_functions="disabled" 84 | ; Check for useless usage of the final attribute 85 | final_attribute_check="enabled" 86 | ; Check for virtual calls in the class constructors 87 | vcall_in_ctor="enabled" 88 | ; Check for useless user defined initializers 89 | useless_initializer="disabled" 90 | ; Check allman brace style 91 | allman_braces_check="disabled" 92 | ; Check for redundant attributes 93 | redundant_attributes_check="enabled" 94 | ; Check public declarations without a documented unittest 95 | has_public_example="disabled" 96 | ; Check for asserts without an explanatory message 97 | assert_without_msg="disabled" 98 | ; Check indent of if constraints 99 | if_constraints_indent="disabled" 100 | ; Check for @trusted applied to a bigger scope than a single function 101 | trust_too_much="enabled" 102 | ; Check for redundant storage classes on variable declarations 103 | redundant_storage_classes="enabled" 104 | ; ModuleFilters for selectively enabling (+std) and disabling (-std.internal) i 105 | ; ndividual checks 106 | [analysis.config.ModuleFilters] 107 | ; Exclude/Import modules 108 | style_check="" 109 | ; Exclude/Import modules 110 | enum_array_literal_check="" 111 | ; Exclude/Import modules 112 | exception_check="" 113 | ; Exclude/Import modules 114 | delete_check="" 115 | ; Exclude/Import modules 116 | float_operator_check="" 117 | ; Exclude/Import modules 118 | number_style_check="" 119 | ; Exclude/Import modules 120 | object_const_check="" 121 | ; Exclude/Import modules 122 | backwards_range_check="" 123 | ; Exclude/Import modules 124 | if_else_same_check="" 125 | ; Exclude/Import modules 126 | constructor_check="" 127 | ; Exclude/Import modules 128 | unused_variable_check="" 129 | ; Exclude/Import modules 130 | unused_label_check="" 131 | ; Exclude/Import modules 132 | unused_parameter_check="" 133 | ; Exclude/Import modules 134 | duplicate_attribute="" 135 | ; Exclude/Import modules 136 | opequals_tohash_check="" 137 | ; Exclude/Import modules 138 | length_subtraction_check="" 139 | ; Exclude/Import modules 140 | builtin_property_names_check="" 141 | ; Exclude/Import modules 142 | asm_style_check="" 143 | ; Exclude/Import modules 144 | logical_precedence_check="" 145 | ; Exclude/Import modules 146 | undocumented_declaration_check="" 147 | ; Exclude/Import modules 148 | function_attribute_check="" 149 | ; Exclude/Import modules 150 | comma_expression_check="" 151 | ; Exclude/Import modules 152 | local_import_check="" 153 | ; Exclude/Import modules 154 | could_be_immutable_check="" 155 | ; Exclude/Import modules 156 | redundant_if_check="" 157 | ; Exclude/Import modules 158 | redundant_parens_check="" 159 | ; Exclude/Import modules 160 | mismatched_args_check="" 161 | ; Exclude/Import modules 162 | label_var_same_name_check="" 163 | ; Exclude/Import modules 164 | long_line_check="" 165 | ; Exclude/Import modules 166 | auto_ref_assignment_check="" 167 | ; Exclude/Import modules 168 | incorrect_infinite_range_check="" 169 | ; Exclude/Import modules 170 | useless_assert_check="" 171 | ; Exclude/Import modules 172 | alias_syntax_check="" 173 | ; Exclude/Import modules 174 | static_if_else_check="" 175 | ; Exclude/Import modules 176 | lambda_return_check="" 177 | ; Exclude/Import modules 178 | auto_function_check="" 179 | ; Exclude/Import modules 180 | imports_sortedness="" 181 | ; Exclude/Import modules 182 | explicitly_annotated_unittests="" 183 | ; Exclude/Import modules 184 | properly_documented_public_functions="" 185 | ; Exclude/Import modules 186 | final_attribute_check="" 187 | ; Exclude/Import modules 188 | vcall_in_ctor="" 189 | ; Exclude/Import modules 190 | useless_initializer="" 191 | ; Exclude/Import modules 192 | allman_braces_check="" 193 | ; Exclude/Import modules 194 | redundant_attributes_check="" 195 | ; Exclude/Import modules 196 | has_public_example="" 197 | ; Exclude/Import modules 198 | assert_without_msg="" 199 | ; Exclude/Import modules 200 | if_constraints_indent="" 201 | ; Exclude/Import modules 202 | trust_too_much="" 203 | ; Exclude/Import modules 204 | redundant_storage_classes="" 205 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluent-asserts", 3 | "authors": [ 4 | "Szabo Bogdan" 5 | ], 6 | "description": "Fluent assertions done right", 7 | "copyright": "Copyright © 2023, Szabo Bogdan", 8 | "license": "MIT", 9 | "homepage": "http://fluentasserts.szabobogdan.com/", 10 | "dependencies": { 11 | "libdparse": "~>0.20.0", 12 | "either": "~>1.1.3", 13 | "ddmp": "~>0.0.1-0.dev.3", 14 | "unit-threaded": { 15 | "version": "*", 16 | "optional": true 17 | } 18 | }, 19 | "configurations": [ 20 | { 21 | "name": "library", 22 | "sourcePaths": [ 23 | "source" 24 | ], 25 | "excludedSourceFiles": [ 26 | "source/updateDocs.d" 27 | ] 28 | }, 29 | { 30 | "name": "unittest", 31 | "targetType": "library", 32 | "dependencies": { 33 | "silly": "~>1.2.0-dev.2" 34 | } 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /source/fluent/asserts.d: -------------------------------------------------------------------------------- 1 | module fluent.asserts; 2 | 3 | public import fluentasserts.core.base; 4 | 5 | version(Have_fluent_asserts_vibe) { 6 | public import fluentasserts.vibe.json; 7 | public import fluentasserts.vibe.request; 8 | } -------------------------------------------------------------------------------- /source/fluentasserts/core/basetype.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.basetype; 2 | 3 | public import fluentasserts.core.base; 4 | import fluentasserts.core.results; 5 | 6 | import std.string; 7 | import std.conv; 8 | import std.algorithm; 9 | 10 | /// When there is a lazy number that throws an it should throw that exception 11 | unittest { 12 | int someLazyInt() { 13 | throw new Exception("This is it."); 14 | } 15 | 16 | ({ 17 | someLazyInt.should.equal(3); 18 | }).should.throwAnyException.withMessage("This is it."); 19 | 20 | ({ 21 | someLazyInt.should.be.greaterThan(3); 22 | }).should.throwAnyException.withMessage("This is it."); 23 | 24 | ({ 25 | someLazyInt.should.be.lessThan(3); 26 | }).should.throwAnyException.withMessage("This is it."); 27 | 28 | ({ 29 | someLazyInt.should.be.between(3, 4); 30 | }).should.throwAnyException.withMessage("This is it."); 31 | 32 | ({ 33 | someLazyInt.should.be.approximately(3, 4); 34 | }).should.throwAnyException.withMessage("This is it."); 35 | } 36 | 37 | @("numbers equal") 38 | unittest { 39 | ({ 40 | 5.should.equal(5); 41 | 5.should.not.equal(6); 42 | }).should.not.throwAnyException; 43 | 44 | auto msg = ({ 45 | 5.should.equal(6); 46 | }).should.throwException!TestException.msg; 47 | 48 | msg.split("\n")[0].should.equal("5 should equal 6. 5 is not equal to 6. "); 49 | 50 | msg = ({ 51 | 5.should.not.equal(5); 52 | }).should.throwException!TestException.msg; 53 | 54 | msg.split("\n")[0].should.equal("5 should not equal 5. 5 is equal to 5. "); 55 | } 56 | 57 | /// bools equal 58 | unittest { 59 | ({ 60 | true.should.equal(true); 61 | true.should.not.equal(false); 62 | }).should.not.throwAnyException; 63 | 64 | auto msg = ({ 65 | true.should.equal(false); 66 | }).should.throwException!TestException.msg; 67 | 68 | msg.split("\n")[0].should.equal("true should equal false. "); 69 | msg.split("\n")[2].strip.should.equal("Expected:false"); 70 | msg.split("\n")[3].strip.should.equal("Actual:true"); 71 | 72 | msg = ({ 73 | true.should.not.equal(true); 74 | }).should.throwException!TestException.msg; 75 | 76 | msg.split("\n")[0].should.equal("true should not equal true. "); 77 | msg.split("\n")[2].strip.should.equal("Expected:not true"); 78 | msg.split("\n")[3].strip.should.equal("Actual:true"); 79 | } 80 | 81 | /// numbers greater than 82 | unittest { 83 | ({ 84 | 5.should.be.greaterThan(4); 85 | 5.should.not.be.greaterThan(6); 86 | 87 | 5.should.be.above(4); 88 | 5.should.not.be.above(6); 89 | }).should.not.throwAnyException; 90 | 91 | auto msg = ({ 92 | 5.should.be.greaterThan(5); 93 | 5.should.be.above(5); 94 | }).should.throwException!TestException.msg; 95 | 96 | msg.split("\n")[0].should.equal("5 should be greater than 5. 5 is less than or equal to 5."); 97 | 98 | msg = ({ 99 | 5.should.not.be.greaterThan(4); 100 | 5.should.not.be.above(4); 101 | }).should.throwException!TestException.msg; 102 | 103 | msg.split("\n")[0].should.equal("5 should not be greater than 4. 5 is greater than 4."); 104 | } 105 | 106 | @("numbers less than") 107 | unittest { 108 | ({ 109 | 5.should.be.lessThan(6); 110 | 5.should.not.be.lessThan(4); 111 | 112 | 5.should.be.below(6); 113 | 5.should.not.be.below(4); 114 | }).should.not.throwAnyException; 115 | 116 | auto msg = ({ 117 | 5.should.be.lessThan(4); 118 | 5.should.be.below(4); 119 | }).should.throwException!TestException.msg; 120 | 121 | msg.split("\n")[0].should.equal("5 should be less than 4. 5 is greater than or equal to 4."); 122 | msg.split("\n")[2].strip.should.equal("Expected:less than 4"); 123 | msg.split("\n")[3].strip.should.equal("Actual:5"); 124 | 125 | msg = ({ 126 | 5.should.not.be.lessThan(6); 127 | 5.should.not.be.below(6); 128 | }).should.throwException!TestException.msg; 129 | 130 | msg.split("\n")[0].should.equal("5 should not be less than 6. 5 is less than 6."); 131 | } 132 | 133 | @("numbers between") 134 | unittest { 135 | ({ 136 | 5.should.be.between(4, 6); 137 | 5.should.be.between(6, 4); 138 | 5.should.not.be.between(5, 6); 139 | 5.should.not.be.between(4, 5); 140 | 141 | 5.should.be.within(4, 6); 142 | 5.should.be.within(6, 4); 143 | 5.should.not.be.within(5, 6); 144 | 5.should.not.be.within(4, 5); 145 | }).should.not.throwAnyException; 146 | 147 | auto msg = ({ 148 | 5.should.be.between(5, 6); 149 | 5.should.be.within(5, 6); 150 | }).should.throwException!TestException.msg; 151 | 152 | msg.split("\n")[0].should.equal("5 should be between 5 and 6. 5 is less than or equal to 5."); 153 | msg.split("\n")[2].strip.should.equal("Expected:a value inside (5, 6) interval"); 154 | msg.split("\n")[3].strip.should.equal("Actual:5"); 155 | 156 | msg = ({ 157 | 5.should.be.between(4, 5); 158 | 5.should.be.within(4, 5); 159 | }).should.throwException!TestException.msg; 160 | 161 | msg.split("\n")[0].should.equal("5 should be between 4 and 5. 5 is greater than or equal to 5."); 162 | msg.split("\n")[2].strip.should.equal("Expected:a value inside (4, 5) interval"); 163 | msg.split("\n")[3].strip.should.equal("Actual:5"); 164 | 165 | msg = ({ 166 | 5.should.not.be.between(4, 6); 167 | 5.should.not.be.within(4, 6); 168 | }).should.throwException!TestException.msg; 169 | 170 | msg.split("\n")[0].strip.should.equal("5 should not be between 4 and 6."); 171 | msg.split("\n")[2].strip.should.equal("Expected:a value outside (4, 6) interval"); 172 | msg.split("\n")[3].strip.should.equal("Actual:5"); 173 | 174 | msg = ({ 175 | 5.should.not.be.between(6, 4); 176 | 5.should.not.be.within(6, 4); 177 | }).should.throwException!TestException.msg; 178 | 179 | msg.split("\n")[0].strip.should.equal("5 should not be between 6 and 4."); 180 | msg.split("\n")[2].strip.should.equal("Expected:a value outside (4, 6) interval"); 181 | msg.split("\n")[3].strip.should.equal("Actual:5"); 182 | } 183 | 184 | /// numbers approximately 185 | unittest { 186 | ({ 187 | (10f/3f).should.be.approximately(3, 0.34); 188 | (10f/3f).should.not.be.approximately(3, 0.1); 189 | }).should.not.throwAnyException; 190 | 191 | auto msg = ({ 192 | (10f/3f).should.be.approximately(3, 0.1); 193 | }).should.throwException!TestException.msg; 194 | 195 | msg.split("\n")[0].strip.should.contain("(10f/3f) should be approximately 3±0.1."); 196 | msg.split("\n")[2].strip.should.contain("Expected:3±0.1"); 197 | msg.split("\n")[3].strip.should.contain("Actual:3.33333"); 198 | 199 | msg = ({ 200 | (10f/3f).should.not.be.approximately(3, 0.34); 201 | }).should.throwException!TestException.msg; 202 | 203 | msg.split("\n")[0].strip.should.contain("(10f/3f) should not be approximately 3±0.34."); 204 | msg.split("\n")[2].strip.should.contain("Expected:not 3±0.34"); 205 | msg.split("\n")[3].strip.should.contain("Actual:3.33333"); 206 | } 207 | 208 | /// should throw exceptions for delegates that return basic types 209 | unittest { 210 | int value() { 211 | throw new Exception("not implemented value"); 212 | } 213 | 214 | void voidValue() { 215 | throw new Exception("nothing here"); 216 | } 217 | 218 | void noException() { } 219 | 220 | value().should.throwAnyException.withMessage.equal("not implemented value"); 221 | voidValue().should.throwAnyException.withMessage.equal("nothing here"); 222 | 223 | bool thrown; 224 | 225 | try { 226 | noException.should.throwAnyException; 227 | } catch (TestException e) { 228 | e.msg.should.startWith("noException should throw any exception. No exception was thrown."); 229 | thrown = true; 230 | } 231 | thrown.should.equal(true); 232 | 233 | thrown = false; 234 | 235 | try { 236 | voidValue().should.not.throwAnyException; 237 | } catch(TestException e) { 238 | thrown = true; 239 | e.msg.split("\n")[0].should.equal("voidValue() should not throw any exception. `object.Exception` saying `nothing here` was thrown."); 240 | } 241 | 242 | thrown.should.equal(true); 243 | } 244 | 245 | /// it should compile const comparison 246 | unittest { 247 | const actual = 42; 248 | actual.should.equal(42); 249 | } 250 | -------------------------------------------------------------------------------- /source/fluentasserts/core/callable.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.callable; 2 | 3 | public import fluentasserts.core.base; 4 | import std.string; 5 | import std.datetime; 6 | import std.conv; 7 | import std.traits; 8 | 9 | import fluentasserts.core.results; 10 | 11 | @safe: 12 | /// 13 | struct ShouldCallable(T) { 14 | private { 15 | T callable; 16 | } 17 | 18 | mixin ShouldCommons; 19 | mixin ShouldThrowableCommons; 20 | 21 | /// 22 | this(lazy T callable) { 23 | auto result = callable.evaluate; 24 | 25 | valueEvaluation = result.evaluation; 26 | this.callable = result.value; 27 | } 28 | 29 | /// 30 | auto haveExecutionTime(string file = __FILE__, size_t line = __LINE__) { 31 | validateException; 32 | 33 | auto tmpShould = ShouldBaseType!Duration(evaluate(valueEvaluation.duration)).forceMessage(" have execution time"); 34 | 35 | return tmpShould; 36 | } 37 | 38 | /// 39 | auto beNull(string file = __FILE__, size_t line = __LINE__) { 40 | validateException; 41 | 42 | addMessage(" be "); 43 | addValue("null"); 44 | beginCheck; 45 | 46 | bool isNull = callable is null; 47 | 48 | string expected; 49 | 50 | static if(isDelegate!callable) { 51 | string actual = callable.ptr.to!string; 52 | } else { 53 | string actual = (cast(void*)callable).to!string; 54 | } 55 | 56 | if(expectedValue) { 57 | expected = "null"; 58 | } else { 59 | expected = "not null"; 60 | } 61 | 62 | return result(isNull, [], new ExpectedActualResult(expected, actual), file, line); 63 | } 64 | } 65 | 66 | /// Should be able to catch any exception 67 | unittest { 68 | ({ 69 | throw new Exception("test"); 70 | }).should.throwAnyException.msg.should.equal("test"); 71 | } 72 | 73 | /// Should be able to catch any assert 74 | unittest { 75 | ({ 76 | assert(false, "test"); 77 | }).should.throwSomething.withMessage.equal("test"); 78 | } 79 | 80 | /// Should be able to use with message without a custom assert 81 | unittest { 82 | ({ 83 | assert(false, "test"); 84 | }).should.throwSomething.withMessage("test"); 85 | } 86 | 87 | /// Should be able to catch a certain exception type 88 | unittest { 89 | class CustomException : Exception { 90 | this(string msg, string fileName = "", size_t line = 0, Throwable next = null) { 91 | super(msg, fileName, line, next); 92 | } 93 | } 94 | 95 | ({ 96 | throw new CustomException("test"); 97 | }).should.throwException!CustomException.withMessage("test"); 98 | 99 | bool hasException; 100 | try { 101 | ({ 102 | throw new Exception("test"); 103 | }).should.throwException!CustomException.withMessage("test"); 104 | } catch(TestException t) { 105 | hasException = true; 106 | t.msg.should.contain(" }) should throw exception with message equal \"test\". `object.Exception` saying `test` was thrown."); 107 | } 108 | hasException.should.equal(true).because("we want to catch a CustomException not an Exception"); 109 | } 110 | 111 | /// Should be able to retrieve a typed version of a custom exception 112 | unittest { 113 | class CustomException : Exception { 114 | int data; 115 | this(int data, string msg, string fileName = "", size_t line = 0, Throwable next = null) { 116 | super(msg, fileName, line, next); 117 | 118 | this.data = data; 119 | } 120 | } 121 | 122 | auto thrown = ({ 123 | throw new CustomException(2, "test"); 124 | }).should.throwException!CustomException.thrown; 125 | 126 | thrown.should.not.beNull; 127 | thrown.msg.should.equal("test"); 128 | (cast(CustomException) thrown).data.should.equal(2); 129 | } 130 | 131 | /// Should fail if an exception is not thrown 132 | unittest { 133 | auto thrown = false; 134 | try { 135 | ({ }).should.throwAnyException; 136 | } catch(TestException e) { 137 | thrown = true; 138 | e.msg.split("\n")[0].should.equal("({ }) should throw any exception. No exception was thrown."); 139 | } 140 | 141 | thrown.should.equal(true); 142 | } 143 | 144 | /// Should fail if an exception is not expected 145 | unittest { 146 | auto thrown = false; 147 | try { 148 | ({ 149 | throw new Exception("test"); 150 | }).should.not.throwAnyException; 151 | } catch(TestException e) { 152 | thrown = true; 153 | e.msg.split("\n")[2].should.equal(" }) should not throw any exception. `object.Exception` saying `test` was thrown."); 154 | } 155 | 156 | thrown.should.equal(true); 157 | } 158 | 159 | /// Should be able to benchmark some code 160 | unittest { 161 | ({ 162 | 163 | }).should.haveExecutionTime.lessThan(1.seconds); 164 | } 165 | 166 | /// Should fail on benchmark timeout 167 | unittest { 168 | import core.thread; 169 | 170 | TestException exception = null; 171 | 172 | try { 173 | ({ 174 | Thread.sleep(2.msecs); 175 | }).should.haveExecutionTime.lessThan(1.msecs); 176 | } catch(TestException e) { 177 | exception = e; 178 | } 179 | 180 | exception.should.not.beNull.because("we wait 20 milliseconds"); 181 | exception.msg.should.startWith("({\n Thread.sleep(2.msecs);\n }) should have execution time less than 1 ms."); 182 | } 183 | 184 | /// It should check if a delegate is null 185 | unittest { 186 | void delegate() action; 187 | action.should.beNull; 188 | 189 | ({ }).should.not.beNull; 190 | 191 | auto msg = ({ 192 | action.should.not.beNull; 193 | }).should.throwException!TestException.msg; 194 | 195 | msg.should.startWith("action should not be null."); 196 | msg.should.contain("Expected:not null"); 197 | msg.should.contain("Actual:null"); 198 | 199 | msg = ({ 200 | ({ }).should.beNull; 201 | }).should.throwException!TestException.msg; 202 | 203 | msg.should.startWith("({ }) should be null."); 204 | msg.should.contain("Expected:null\n"); 205 | msg.should.not.contain("Actual:null\n"); 206 | } 207 | -------------------------------------------------------------------------------- /source/fluentasserts/core/expect.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.expect; 2 | 3 | import fluentasserts.core.lifecycle; 4 | import fluentasserts.core.evaluation; 5 | import fluentasserts.core.results; 6 | 7 | import fluentasserts.core.serializers; 8 | 9 | import std.traits; 10 | import std.string; 11 | import std.uni; 12 | import std.conv; 13 | 14 | /// 15 | @safe struct Expect { 16 | 17 | private { 18 | Evaluation evaluation; 19 | int refCount; 20 | } 21 | 22 | this(ValueEvaluation value) @trusted { 23 | this.evaluation = new Evaluation(); 24 | 25 | evaluation.id = Lifecycle.instance.beginEvaluation(value); 26 | evaluation.currentValue = value; 27 | evaluation.message = new MessageResult(); 28 | evaluation.source = new SourceResult(value.fileName, value.line); 29 | 30 | try { 31 | auto sourceValue = evaluation.source.getValue; 32 | 33 | if(sourceValue == "") { 34 | evaluation.message.startWith(evaluation.currentValue.niceValue); 35 | } else { 36 | evaluation.message.startWith(sourceValue); 37 | } 38 | } catch(Exception) { 39 | evaluation.message.startWith(evaluation.currentValue.strValue); 40 | } 41 | 42 | evaluation.message.addText(" should"); 43 | 44 | if(value.prependText) { 45 | evaluation.message.addText(value.prependText); 46 | } 47 | } 48 | 49 | this(ref return scope Expect another) { 50 | this.evaluation = another.evaluation; 51 | this.refCount = another.refCount + 1; 52 | } 53 | 54 | ~this() { 55 | refCount--; 56 | 57 | if(refCount < 0) { 58 | evaluation.message.addText(" "); 59 | evaluation.message.addText(evaluation.operationName.toNiceOperation); 60 | 61 | if(evaluation.expectedValue.niceValue) { 62 | evaluation.message.addText(" "); 63 | evaluation.message.addValue(evaluation.expectedValue.niceValue); 64 | } else if(evaluation.expectedValue.strValue) { 65 | evaluation.message.addText(" "); 66 | evaluation.message.addValue(evaluation.expectedValue.strValue); 67 | } 68 | 69 | Lifecycle.instance.endEvaluation(evaluation); 70 | } 71 | } 72 | 73 | string msg(const size_t line = __LINE__, const string file = __FILE__) @trusted { 74 | if(this.thrown is null) { 75 | throw new Exception("There were no thrown exceptions", file, line); 76 | } 77 | 78 | return this.thrown.message.to!string; 79 | } 80 | 81 | Expect withMessage(const size_t line = __LINE__, const string file = __FILE__) { 82 | addOperationName("withMessage"); 83 | return this; 84 | } 85 | 86 | Expect withMessage(string message, const size_t line = __LINE__, const string file = __FILE__) { 87 | addOperationName("withMessage"); 88 | return this.equal(message); 89 | } 90 | 91 | Throwable thrown() { 92 | Lifecycle.instance.endEvaluation(evaluation); 93 | return evaluation.throwable; 94 | } 95 | 96 | /// 97 | Expect to() { 98 | return this; 99 | } 100 | 101 | /// 102 | Expect be () { 103 | evaluation.message.addText(" be"); 104 | return this; 105 | } 106 | 107 | /// 108 | Expect not() { 109 | evaluation.isNegated = !evaluation.isNegated; 110 | evaluation.message.addText(" not"); 111 | 112 | return this; 113 | } 114 | 115 | /// 116 | auto throwAnyException() { 117 | return opDispatch!"throwAnyException"; 118 | } 119 | 120 | /// 121 | Expect throwException(Type)() { 122 | this.evaluation.expectedValue.meta["exceptionType"] = fullyQualifiedName!Type; 123 | this.evaluation.expectedValue.meta["throwableType"] = fullyQualifiedName!Type; 124 | 125 | return opDispatch!"throwException"(fullyQualifiedName!Type); 126 | } 127 | 128 | auto because(string reason) { 129 | evaluation.message.prependText("Because " ~ reason ~ ", "); 130 | return this; 131 | } 132 | 133 | /// 134 | auto equal(T)(T value) { 135 | return opDispatch!"equal"(value); 136 | } 137 | 138 | /// 139 | auto contain(T)(T value) { 140 | return opDispatch!"contain"(value); 141 | } 142 | 143 | /// 144 | auto greaterThan(T)(T value) { 145 | return opDispatch!"greaterThan"(value); 146 | } 147 | 148 | /// 149 | auto greaterOrEqualTo(T)(T value) { 150 | return opDispatch!"greaterOrEqualTo"(value); 151 | } 152 | 153 | /// 154 | auto above(T)(T value) { 155 | return opDispatch!"above"(value); 156 | } 157 | /// 158 | auto lessThan(T)(T value) { 159 | return opDispatch!"lessThan"(value); 160 | } 161 | 162 | /// 163 | auto lessOrEqualTo(T)(T value) { 164 | return opDispatch!"lessOrEqualTo"(value); 165 | } 166 | 167 | /// 168 | auto below(T)(T value) { 169 | return opDispatch!"below"(value); 170 | } 171 | 172 | /// 173 | auto startWith(T)(T value) { 174 | return opDispatch!"startWith"(value); 175 | } 176 | 177 | /// 178 | auto endWith(T)(T value) { 179 | return opDispatch!"endWith"(value); 180 | } 181 | 182 | auto containOnly(T)(T value) { 183 | return opDispatch!"containOnly"(value); 184 | } 185 | 186 | auto beNull() { 187 | return opDispatch!"beNull"; 188 | } 189 | 190 | auto instanceOf(Type)() { 191 | return opDispatch!"instanceOf"(fullyQualifiedName!Type); 192 | } 193 | 194 | auto approximately(T, U)(T value, U range) { 195 | return opDispatch!"approximately"(value, range); 196 | } 197 | 198 | auto between(T, U)(T value, U range) { 199 | return opDispatch!"between"(value, range); 200 | } 201 | 202 | auto within(T, U)(T value, U range) { 203 | return opDispatch!"within"(value, range); 204 | } 205 | 206 | void inhibit() { 207 | this.refCount = int.max; 208 | } 209 | 210 | auto haveExecutionTime() { 211 | this.inhibit; 212 | 213 | auto result = expect(evaluation.currentValue.duration, evaluation.source.file, evaluation.source.line, " have execution time"); 214 | 215 | return result; 216 | } 217 | 218 | void addOperationName(string value) { 219 | 220 | if(this.evaluation.operationName) { 221 | this.evaluation.operationName ~= "."; 222 | } 223 | 224 | this.evaluation.operationName ~= value; 225 | } 226 | 227 | /// 228 | Expect opDispatch(string methodName)() { 229 | addOperationName(methodName); 230 | 231 | return this; 232 | } 233 | 234 | /// 235 | Expect opDispatch(string methodName, Params...)(Params params) if(Params.length > 0) { 236 | addOperationName(methodName); 237 | 238 | static if(Params.length > 0) { 239 | auto expectedValue = params[0].evaluate.evaluation; 240 | 241 | foreach(key, value; evaluation.expectedValue.meta) { 242 | expectedValue.meta[key] = value; 243 | } 244 | 245 | evaluation.expectedValue = expectedValue; 246 | } 247 | 248 | static if(Params.length >= 1) { 249 | static foreach (i, Param; Params) { 250 | () @trusted { evaluation.expectedValue.meta[i.to!string] = SerializerRegistry.instance.serialize(params[i]); } (); 251 | } 252 | } 253 | 254 | return this; 255 | } 256 | } 257 | 258 | /// 259 | Expect expect(void delegate() callable, const string file = __FILE__, const size_t line = __LINE__, string prependText = null) @trusted { 260 | ValueEvaluation value; 261 | value.typeNames = [ "callable" ]; 262 | 263 | try { 264 | if(callable !is null) { 265 | callable(); 266 | } else { 267 | value.typeNames = ["null"]; 268 | } 269 | } catch(Exception e) { 270 | value.throwable = e; 271 | value.meta["Exception"] = "yes"; 272 | } catch(Throwable t) { 273 | value.throwable = t; 274 | value.meta["Throwable"] = "yes"; 275 | } 276 | 277 | value.fileName = file; 278 | value.line = line; 279 | value.prependText = prependText; 280 | 281 | return Expect(value); 282 | } 283 | 284 | /// 285 | Expect expect(T)(lazy T testedValue, const string file = __FILE__, const size_t line = __LINE__, string prependText = null) @trusted { 286 | return Expect(testedValue.evaluate(file, line, prependText).evaluation); 287 | } 288 | 289 | /// 290 | string toNiceOperation(string value) @safe nothrow { 291 | string newValue; 292 | 293 | foreach(index, ch; value) { 294 | if(index == 0) { 295 | newValue ~= ch.toLower; 296 | continue; 297 | } 298 | 299 | if(ch == '.') { 300 | newValue ~= ' '; 301 | continue; 302 | } 303 | 304 | if(ch.isUpper && value[index - 1].isLower) { 305 | newValue ~= ' '; 306 | newValue ~= ch.toLower; 307 | continue; 308 | } 309 | 310 | newValue ~= ch; 311 | } 312 | 313 | return newValue; 314 | } 315 | 316 | /// toNiceOperation converts to a nice and readable string 317 | unittest { 318 | expect("".toNiceOperation).to.equal(""); 319 | expect("a.b".toNiceOperation).to.equal("a b"); 320 | expect("aB".toNiceOperation).to.equal("a b"); 321 | } 322 | -------------------------------------------------------------------------------- /source/fluentasserts/core/message.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.message; 2 | 3 | import std.string; 4 | import ddmp.diff; 5 | import fluentasserts.core.results; 6 | import std.algorithm; 7 | import std.conv; 8 | 9 | @safe: 10 | 11 | /// Glyphs used to display special chars in the results 12 | struct ResultGlyphs { 13 | static { 14 | /// Glyph for the tab char 15 | string tab; 16 | 17 | /// Glyph for the \r char 18 | string carriageReturn; 19 | 20 | /// Glyph for the \n char 21 | string newline; 22 | 23 | /// Glyph for the space char 24 | string space; 25 | 26 | /// Glyph for the \0 char 27 | string nullChar; 28 | 29 | /// Glyph that indicates the error line 30 | string sourceIndicator; 31 | 32 | /// Glyph that sepparates the line number 33 | string sourceLineSeparator; 34 | 35 | /// Glyph for the diff begin indicator 36 | string diffBegin; 37 | 38 | /// Glyph for the diff end indicator 39 | string diffEnd; 40 | 41 | /// Glyph that marks an inserted text in diff 42 | string diffInsert; 43 | 44 | /// Glyph that marks deleted text in diff 45 | string diffDelete; 46 | } 47 | 48 | /// Set the default values. The values are 49 | static resetDefaults() { 50 | version(windows) { 51 | ResultGlyphs.tab = `\t`; 52 | ResultGlyphs.carriageReturn = `\r`; 53 | ResultGlyphs.newline = `\n`; 54 | ResultGlyphs.space = ` `; 55 | ResultGlyphs.nullChar = `␀`; 56 | } else { 57 | ResultGlyphs.tab = `¤`; 58 | ResultGlyphs.carriageReturn = `←`; 59 | ResultGlyphs.newline = `↲`; 60 | ResultGlyphs.space = `᛫`; 61 | ResultGlyphs.nullChar = `\0`; 62 | } 63 | 64 | ResultGlyphs.sourceIndicator = ">"; 65 | ResultGlyphs.sourceLineSeparator = ":"; 66 | 67 | ResultGlyphs.diffBegin = "["; 68 | ResultGlyphs.diffEnd = "]"; 69 | ResultGlyphs.diffInsert = "+"; 70 | ResultGlyphs.diffDelete = "-"; 71 | } 72 | } 73 | 74 | struct Message { 75 | enum Type { 76 | info, 77 | value, 78 | title, 79 | category, 80 | insert, 81 | delete_ 82 | } 83 | 84 | Type type; 85 | string text; 86 | 87 | this(Type type, string text) nothrow { 88 | this.type = type; 89 | 90 | if(type == Type.value || type == Type.insert || type == Type.delete_) { 91 | this.text = text 92 | .replace("\r", ResultGlyphs.carriageReturn) 93 | .replace("\n", ResultGlyphs.newline) 94 | .replace("\0", ResultGlyphs.nullChar) 95 | .replace("\t", ResultGlyphs.tab); 96 | } else { 97 | this.text = text; 98 | } 99 | } 100 | 101 | string toString() nothrow inout { 102 | switch(type) { 103 | case Type.title: 104 | return "\n\n" ~ text ~ "\n"; 105 | case Type.insert: 106 | return "[-" ~ text ~ "]"; 107 | case Type.delete_: 108 | return "[+" ~ text ~ "]"; 109 | case Type.category: 110 | return "\n" ~ text ~ ""; 111 | default: 112 | return text; 113 | } 114 | } 115 | } 116 | 117 | IResult[] toException(ref EvaluationResult result) nothrow { 118 | if(result.messages.length == 0) { 119 | return []; 120 | } 121 | 122 | return [ new EvaluationResultInstance(result) ]; 123 | } 124 | 125 | struct EvaluationResult { 126 | private { 127 | immutable(Message)[] messages; 128 | } 129 | 130 | void add(immutable(Message) message) nothrow { 131 | messages ~= message; 132 | } 133 | 134 | string toString() nothrow { 135 | string result; 136 | 137 | foreach (message; messages) { 138 | result ~= message.toString; 139 | } 140 | 141 | return result; 142 | } 143 | 144 | void print(ResultPrinter printer) nothrow { 145 | foreach (message; messages) { 146 | printer.print(message); 147 | } 148 | } 149 | } 150 | 151 | static immutable actualTitle = Message(Message.Type.category, "Actual:"); 152 | 153 | void addResult(ref EvaluationResult result, string value) nothrow @trusted { 154 | result.add(actualTitle); 155 | 156 | result.add(Message(Message.Type.value, value)); 157 | } 158 | 159 | 160 | static immutable expectedTitle = Message(Message.Type.category, "Expected:"); 161 | static immutable expectedNot = Message(Message.Type.info, "not "); 162 | 163 | void addExpected(ref EvaluationResult result, bool isNegated, string value) nothrow @trusted { 164 | result.add(expectedTitle); 165 | 166 | if(isNegated) { 167 | result.add(expectedNot); 168 | } 169 | 170 | result.add(Message(Message.Type.value, value)); 171 | } 172 | 173 | 174 | static immutable diffTitle = Message(Message.Type.title, "Diff:"); 175 | 176 | void addDiff(ref EvaluationResult result, string actual, string expected) nothrow @trusted { 177 | result.add(diffTitle); 178 | 179 | try { 180 | auto diffResult = diff_main(expected, actual); 181 | 182 | foreach(diff; diffResult) { 183 | if(diff.operation == Operation.EQUAL) { 184 | result.add(Message(Message.Type.info, diff.text.to!string)); 185 | } 186 | 187 | if(diff.operation == Operation.INSERT) { 188 | result.add(Message(Message.Type.insert, diff.text.to!string)); 189 | } 190 | 191 | if(diff.operation == Operation.DELETE) { 192 | result.add(Message(Message.Type.delete_, diff.text.to!string)); 193 | } 194 | } 195 | } catch(Exception e) { 196 | return; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /source/fluentasserts/core/objects.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.objects; 2 | 3 | public import fluentasserts.core.base; 4 | import fluentasserts.core.results; 5 | 6 | import std.string; 7 | import std.stdio; 8 | import std.traits; 9 | import std.conv; 10 | 11 | /// When there is a lazy object that throws an it should throw that exception 12 | unittest { 13 | Object someLazyObject() { 14 | throw new Exception("This is it."); 15 | } 16 | 17 | ({ 18 | someLazyObject.should.not.beNull; 19 | }).should.throwAnyException.withMessage("This is it."); 20 | 21 | ({ 22 | someLazyObject.should.be.instanceOf!Object; 23 | }).should.throwAnyException.withMessage("This is it."); 24 | 25 | ({ 26 | someLazyObject.should.equal(new Object); 27 | }).should.throwAnyException.withMessage("This is it."); 28 | } 29 | 30 | /// object beNull 31 | unittest { 32 | Object o = null; 33 | 34 | ({ 35 | o.should.beNull; 36 | (new Object).should.not.beNull; 37 | }).should.not.throwAnyException; 38 | 39 | auto msg = ({ 40 | o.should.not.beNull; 41 | }).should.throwException!TestException.msg; 42 | 43 | msg.split("\n")[0].should.equal("o should not be null."); 44 | msg.split("\n")[2].strip.should.equal("Expected:not null"); 45 | msg.split("\n")[3].strip.should.equal("Actual:object.Object"); 46 | 47 | msg = ({ 48 | (new Object).should.beNull; 49 | }).should.throwException!TestException.msg; 50 | 51 | msg.split("\n")[0].should.equal("(new Object) should be null."); 52 | msg.split("\n")[2].strip.should.equal("Expected:null"); 53 | msg.split("\n")[3].strip.strip.should.equal("Actual:object.Object"); 54 | } 55 | 56 | /// object instanceOf 57 | unittest { 58 | class BaseClass { } 59 | class ExtendedClass : BaseClass { } 60 | class SomeClass { } 61 | class OtherClass { } 62 | 63 | auto someObject = new SomeClass; 64 | auto otherObject = new OtherClass; 65 | auto extendedObject = new ExtendedClass; 66 | 67 | someObject.should.be.instanceOf!SomeClass; 68 | extendedObject.should.be.instanceOf!BaseClass; 69 | 70 | someObject.should.not.be.instanceOf!OtherClass; 71 | someObject.should.not.be.instanceOf!BaseClass; 72 | 73 | auto msg = ({ 74 | otherObject.should.be.instanceOf!SomeClass; 75 | }).should.throwException!TestException.msg; 76 | 77 | msg.split("\n")[0].should.startWith(`otherObject should be instance of "fluentasserts.core.objects.__unittest_L57_C1.SomeClass".`); 78 | msg.split("\n")[2].strip.should.equal("Expected:typeof fluentasserts.core.objects.__unittest_L57_C1.SomeClass"); 79 | msg.split("\n")[3].strip.should.equal("Actual:typeof fluentasserts.core.objects.__unittest_L57_C1.OtherClass"); 80 | 81 | msg = ({ 82 | otherObject.should.not.be.instanceOf!OtherClass; 83 | }).should.throwException!TestException.msg; 84 | 85 | msg.split("\n")[0].should.startWith(`otherObject should not be instance of "fluentasserts.core.objects.__unittest_L57_C1.OtherClass"`); 86 | msg.split("\n")[2].strip.should.equal("Expected:not typeof fluentasserts.core.objects.__unittest_L57_C1.OtherClass"); 87 | msg.split("\n")[3].strip.should.equal("Actual:typeof fluentasserts.core.objects.__unittest_L57_C1.OtherClass"); 88 | } 89 | 90 | /// object instanceOf interface 91 | unittest { 92 | interface MyInterface { } 93 | class BaseClass : MyInterface { } 94 | class OtherClass { } 95 | 96 | auto someObject = new BaseClass; 97 | MyInterface someInterface = new BaseClass; 98 | auto otherObject = new OtherClass; 99 | 100 | someInterface.should.be.instanceOf!MyInterface; 101 | someInterface.should.not.be.instanceOf!BaseClass; 102 | 103 | someObject.should.be.instanceOf!MyInterface; 104 | 105 | auto msg = ({ 106 | otherObject.should.be.instanceOf!MyInterface; 107 | }).should.throwException!TestException.msg; 108 | 109 | msg.split("\n")[0].should.startWith(`otherObject should be instance of "fluentasserts.core.objects.__unittest_L91_C1.MyInterface".`); 110 | msg.split("\n")[2].strip.should.equal("Expected:typeof fluentasserts.core.objects.__unittest_L91_C1.MyInterface"); 111 | msg.split("\n")[3].strip.should.equal("Actual:typeof fluentasserts.core.objects.__unittest_L91_C1.OtherClass"); 112 | 113 | msg = ({ 114 | someObject.should.not.be.instanceOf!MyInterface; 115 | }).should.throwException!TestException.msg; 116 | 117 | msg.split("\n")[0].should.contain(`someObject should not be instance of "fluentasserts.core.objects.__unittest_L91_C1.MyInterface".`); 118 | msg.split("\n")[2].strip.should.equal("Expected:not typeof fluentasserts.core.objects.__unittest_L91_C1.MyInterface"); 119 | msg.split("\n")[3].strip.should.equal("Actual:typeof fluentasserts.core.objects.__unittest_L91_C1.BaseClass"); 120 | } 121 | 122 | /// should throw exceptions for delegates that return basic types 123 | unittest { 124 | class SomeClass { } 125 | 126 | SomeClass value() { 127 | throw new Exception("not implemented"); 128 | } 129 | 130 | SomeClass noException() { return null; } 131 | 132 | value().should.throwAnyException.withMessage.equal("not implemented"); 133 | 134 | bool thrown; 135 | 136 | try { 137 | noException.should.throwAnyException; 138 | } catch (TestException e) { 139 | e.msg.should.startWith("noException should throw any exception. No exception was thrown."); 140 | thrown = true; 141 | } 142 | 143 | thrown.should.equal(true); 144 | } 145 | 146 | /// object equal 147 | unittest { 148 | class TestEqual { 149 | private int value; 150 | 151 | this(int value) { 152 | this.value = value; 153 | } 154 | } 155 | 156 | auto instance = new TestEqual(1); 157 | 158 | instance.should.equal(instance); 159 | instance.should.not.equal(new TestEqual(1)); 160 | 161 | auto msg = ({ 162 | instance.should.not.equal(instance); 163 | }).should.throwException!TestException.msg; 164 | 165 | msg.should.startWith("instance should not equal TestEqual"); 166 | 167 | msg = ({ 168 | instance.should.equal(new TestEqual(1)); 169 | }).should.throwException!TestException.msg; 170 | 171 | msg.should.startWith("instance should equal TestEqual"); 172 | } 173 | 174 | /// null object comparison 175 | unittest 176 | { 177 | Object nullObject; 178 | 179 | auto msg = ({ 180 | nullObject.should.equal(new Object); 181 | }).should.throwException!TestException.msg; 182 | 183 | msg.should.startWith("nullObject should equal Object("); 184 | 185 | msg = ({ 186 | (new Object).should.equal(null); 187 | }).should.throwException!TestException.msg; 188 | 189 | msg.should.startWith("(new Object) should equal null."); 190 | } 191 | -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/approximately.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.approximately; 2 | 3 | import fluentasserts.core.results; 4 | import fluentasserts.core.evaluation; 5 | import fluentasserts.core.array; 6 | import fluentasserts.core.serializers; 7 | import fluentasserts.core.operations.contain; 8 | 9 | import fluentasserts.core.lifecycle; 10 | 11 | import std.algorithm; 12 | import std.array; 13 | import std.conv; 14 | import std.math; 15 | 16 | version(unittest) { 17 | import fluentasserts.core.expect; 18 | } 19 | 20 | static immutable approximatelyDescription = "Asserts that the target is a number that's within a given +/- `delta` range of the given number expected. However, it's often best to assert that the target is equal to its expected value."; 21 | 22 | /// 23 | IResult[] approximately(ref Evaluation evaluation) @trusted nothrow { 24 | IResult[] results = []; 25 | 26 | evaluation.message.addValue("±"); 27 | evaluation.message.addValue(evaluation.expectedValue.meta["1"]); 28 | evaluation.message.addText("."); 29 | 30 | real current; 31 | real expected; 32 | real delta; 33 | 34 | try { 35 | current = evaluation.currentValue.strValue.to!real; 36 | expected = evaluation.expectedValue.strValue.to!real; 37 | delta = evaluation.expectedValue.meta["1"].to!real; 38 | } catch(Exception e) { 39 | results ~= new MessageResult("Can't parse the provided arguments!"); 40 | 41 | return results; 42 | } 43 | 44 | string strExpected = evaluation.expectedValue.strValue ~ "±" ~ evaluation.expectedValue.meta["1"]; 45 | string strCurrent = evaluation.currentValue.strValue; 46 | 47 | auto result = isClose(current, expected, 0, delta); 48 | 49 | if(evaluation.isNegated) { 50 | result = !result; 51 | } 52 | 53 | if(result) { 54 | return []; 55 | } 56 | 57 | if(evaluation.currentValue.typeName != "bool") { 58 | evaluation.message.addText(" "); 59 | evaluation.message.addValue(strCurrent); 60 | 61 | if(evaluation.isNegated) { 62 | evaluation.message.addText(" is approximately "); 63 | } else { 64 | evaluation.message.addText(" is not approximately "); 65 | } 66 | 67 | evaluation.message.addValue(strExpected); 68 | evaluation.message.addText("."); 69 | } 70 | 71 | try results ~= new ExpectedActualResult((evaluation.isNegated ? "not " : "") ~ strExpected, strCurrent); catch(Exception) {} 72 | 73 | return results; 74 | } 75 | 76 | /// 77 | IResult[] approximatelyList(ref Evaluation evaluation) @trusted nothrow { 78 | evaluation.message.addValue("±" ~ evaluation.expectedValue.meta["1"]); 79 | evaluation.message.addText("."); 80 | 81 | double maxRelDiff; 82 | real[] testData; 83 | real[] expectedPieces; 84 | 85 | try { 86 | testData = evaluation.currentValue.strValue.parseList.cleanString.map!(a => a.to!real).array; 87 | expectedPieces = evaluation.expectedValue.strValue.parseList.cleanString.map!(a => a.to!real).array; 88 | maxRelDiff = evaluation.expectedValue.meta["1"].to!double; 89 | } catch(Exception e) { 90 | return [ new MessageResult("Can not perform the assert.") ]; 91 | } 92 | 93 | auto comparison = ListComparison!real(testData, expectedPieces, maxRelDiff); 94 | 95 | auto missing = comparison.missing; 96 | auto extra = comparison.extra; 97 | auto common = comparison.common; 98 | 99 | IResult[] results = []; 100 | 101 | bool allEqual = testData.length == expectedPieces.length; 102 | 103 | if(allEqual) { 104 | foreach(i; 0..testData.length) { 105 | allEqual = allEqual && isClose(testData[i], expectedPieces[i], 0, maxRelDiff) && true; 106 | } 107 | } 108 | 109 | string strExpected; 110 | string strMissing; 111 | 112 | if(maxRelDiff == 0) { 113 | strExpected = evaluation.expectedValue.strValue; 114 | try strMissing = missing.length == 0 ? "" : missing.to!string; 115 | catch(Exception) {} 116 | } else try { 117 | strMissing = "[" ~ missing.map!(a => a.to!string ~ "±" ~ maxRelDiff.to!string).join(", ") ~ "]"; 118 | strExpected = "[" ~ expectedPieces.map!(a => a.to!string ~ "±" ~ maxRelDiff.to!string).join(", ") ~ "]"; 119 | } catch(Exception) {} 120 | 121 | if(!evaluation.isNegated) { 122 | if(!allEqual) { 123 | try results ~= new ExpectedActualResult(strExpected, evaluation.currentValue.strValue); 124 | catch(Exception) {} 125 | 126 | try results ~= new ExtraMissingResult(extra.length == 0 ? "" : extra.to!string, strMissing); 127 | catch(Exception) {} 128 | } 129 | } else { 130 | if(allEqual) { 131 | try results ~= new ExpectedActualResult("not " ~ strExpected, evaluation.currentValue.strValue); 132 | catch(Exception) {} 133 | } 134 | } 135 | 136 | return results; 137 | } 138 | -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/arrayEqual.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.arrayEqual; 2 | 3 | import fluentasserts.core.results; 4 | import fluentasserts.core.evaluation; 5 | 6 | import fluentasserts.core.lifecycle; 7 | 8 | version(unittest) { 9 | import fluentasserts.core.expect; 10 | } 11 | 12 | static immutable arrayEqualDescription = "Asserts that the target is strictly == equal to the given val."; 13 | 14 | /// 15 | IResult[] arrayEqual(ref Evaluation evaluation) @safe nothrow { 16 | evaluation.message.addText("."); 17 | bool result = true; 18 | 19 | EquableValue[] expectedPieces = evaluation.expectedValue.proxyValue.toArray; 20 | EquableValue[] testData = evaluation.currentValue.proxyValue.toArray; 21 | 22 | if(testData.length == expectedPieces.length) { 23 | foreach(index, testedValue; testData) { 24 | if(testedValue !is null && !testedValue.isEqualTo(expectedPieces[index])) { 25 | result = false; 26 | break; 27 | } 28 | } 29 | } else { 30 | result = false; 31 | } 32 | 33 | if(evaluation.isNegated) { 34 | result = !result; 35 | } 36 | 37 | if(result) { 38 | return []; 39 | } 40 | 41 | IResult[] results = []; 42 | 43 | if(evaluation.isNegated) { 44 | try results ~= new ExpectedActualResult("not " ~ evaluation.expectedValue.strValue, evaluation.currentValue.strValue); catch(Exception) {} 45 | } else { 46 | try results ~= new DiffResult(evaluation.expectedValue.strValue, evaluation.currentValue.strValue); catch(Exception) {} 47 | try results ~= new ExpectedActualResult(evaluation.expectedValue.strValue, evaluation.currentValue.strValue); catch(Exception) {} 48 | } 49 | 50 | return results; 51 | } 52 | -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/beNull.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.beNull; 2 | 3 | import fluentasserts.core.results; 4 | import fluentasserts.core.evaluation; 5 | 6 | import fluentasserts.core.lifecycle; 7 | import std.algorithm; 8 | 9 | static immutable beNullDescription = "Asserts that the value is null."; 10 | 11 | /// 12 | IResult[] beNull(ref Evaluation evaluation) @safe nothrow { 13 | evaluation.message.addText("."); 14 | 15 | auto result = evaluation.currentValue.typeNames.canFind("null") || evaluation.currentValue.strValue == "null"; 16 | 17 | if(evaluation.isNegated) { 18 | result = !result; 19 | } 20 | 21 | if(result) { 22 | return []; 23 | } 24 | 25 | IResult[] results = []; 26 | 27 | try results ~= new ExpectedActualResult( 28 | evaluation.isNegated ? "not null" : "null", 29 | evaluation.currentValue.typeNames.length ? evaluation.currentValue.typeNames[0] : "unknown"); 30 | catch(Exception) {} 31 | 32 | return results; 33 | } 34 | -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/between.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.between; 2 | 3 | import fluentasserts.core.results; 4 | import fluentasserts.core.evaluation; 5 | 6 | import fluentasserts.core.lifecycle; 7 | 8 | import std.conv; 9 | import std.datetime; 10 | 11 | version(unittest) { 12 | import fluentasserts.core.expect; 13 | } 14 | 15 | static immutable betweenDescription = "Asserts that the target is a number or a date greater than or equal to the given number or date start, " ~ 16 | "and less than or equal to the given number or date finish respectively. However, it's often best to assert that the target is equal to its expected value."; 17 | 18 | /// 19 | IResult[] between(T)(ref Evaluation evaluation) @safe nothrow { 20 | evaluation.message.addText(" and "); 21 | evaluation.message.addValue(evaluation.expectedValue.meta["1"]); 22 | evaluation.message.addText(". "); 23 | 24 | T currentValue; 25 | T limit1; 26 | T limit2; 27 | 28 | try { 29 | currentValue = evaluation.currentValue.strValue.to!T; 30 | limit1 = evaluation.expectedValue.strValue.to!T; 31 | limit2 = evaluation.expectedValue.meta["1"].to!T; 32 | } catch(Exception e) { 33 | return [ new MessageResult("Can't convert the values to " ~ T.stringof) ]; 34 | } 35 | 36 | return betweenResults(currentValue, limit1, limit2, evaluation); 37 | } 38 | 39 | 40 | /// 41 | IResult[] betweenDuration(ref Evaluation evaluation) @safe nothrow { 42 | evaluation.message.addText(" and "); 43 | 44 | Duration currentValue; 45 | Duration limit1; 46 | Duration limit2; 47 | 48 | try { 49 | currentValue = dur!"nsecs"(evaluation.currentValue.strValue.to!size_t); 50 | limit1 = dur!"nsecs"(evaluation.expectedValue.strValue.to!size_t); 51 | limit2 = dur!"nsecs"(evaluation.expectedValue.meta["1"].to!size_t); 52 | 53 | evaluation.message.addValue(limit2.to!string); 54 | } catch(Exception e) { 55 | return [ new MessageResult("Can't convert the values to Duration") ]; 56 | } 57 | 58 | evaluation.message.addText(". "); 59 | 60 | return betweenResults(currentValue, limit1, limit2, evaluation); 61 | } 62 | 63 | /// 64 | IResult[] betweenSysTime(ref Evaluation evaluation) @safe nothrow { 65 | evaluation.message.addText(" and "); 66 | 67 | SysTime currentValue; 68 | SysTime limit1; 69 | SysTime limit2; 70 | 71 | try { 72 | currentValue = SysTime.fromISOExtString(evaluation.currentValue.strValue); 73 | limit1 = SysTime.fromISOExtString(evaluation.expectedValue.strValue); 74 | limit2 = SysTime.fromISOExtString(evaluation.expectedValue.meta["1"]); 75 | 76 | evaluation.message.addValue(limit2.toISOExtString); 77 | } catch(Exception e) { 78 | return [ new MessageResult("Can't convert the values to Duration") ]; 79 | } 80 | 81 | evaluation.message.addText(". "); 82 | 83 | return betweenResults(currentValue, limit1, limit2, evaluation); 84 | } 85 | 86 | private IResult[] betweenResults(T)(T currentValue, T limit1, T limit2, ref Evaluation evaluation) { 87 | T min = limit1 < limit2 ? limit1 : limit2; 88 | T max = limit1 > limit2 ? limit1 : limit2; 89 | 90 | auto isLess = currentValue <= min; 91 | auto isGreater = currentValue >= max; 92 | auto isBetween = !isLess && !isGreater; 93 | 94 | string interval; 95 | 96 | try { 97 | interval = "a value " ~ (evaluation.isNegated ? "outside" : "inside") ~ " (" ~ min.to!string ~ ", " ~ max.to!string ~ ") interval"; 98 | } catch(Exception) { 99 | interval = "a value " ~ (evaluation.isNegated ? "outside" : "inside") ~ " the interval"; 100 | } 101 | 102 | IResult[] results = []; 103 | 104 | if(!evaluation.isNegated) { 105 | if(!isBetween) { 106 | evaluation.message.addValue(evaluation.currentValue.niceValue); 107 | 108 | if(isGreater) { 109 | evaluation.message.addText(" is greater than or equal to "); 110 | try evaluation.message.addValue(max.to!string); 111 | catch(Exception) {} 112 | } 113 | 114 | if(isLess) { 115 | evaluation.message.addText(" is less than or equal to "); 116 | try evaluation.message.addValue(min.to!string); 117 | catch(Exception) {} 118 | } 119 | 120 | evaluation.message.addText("."); 121 | 122 | results ~= new ExpectedActualResult(interval, evaluation.currentValue.niceValue); 123 | } 124 | } else if(isBetween) { 125 | results ~= new ExpectedActualResult(interval, evaluation.currentValue.niceValue); 126 | } 127 | 128 | return results; 129 | } 130 | -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/contain.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.contain; 2 | 3 | import std.algorithm; 4 | import std.array; 5 | import std.conv; 6 | 7 | import fluentasserts.core.array; 8 | import fluentasserts.core.results; 9 | import fluentasserts.core.evaluation; 10 | import fluentasserts.core.serializers; 11 | 12 | import fluentasserts.core.lifecycle; 13 | 14 | version(unittest) { 15 | import fluentasserts.core.expect; 16 | } 17 | 18 | static immutable containDescription = "When the tested value is a string, it asserts that the given string val is a substring of the target. \n\n" ~ 19 | "When the tested value is an array, it asserts that the given val is inside the tested value."; 20 | 21 | /// 22 | IResult[] contain(ref Evaluation evaluation) @safe nothrow { 23 | evaluation.message.addText("."); 24 | 25 | IResult[] results = []; 26 | 27 | auto expectedPieces = evaluation.expectedValue.strValue.parseList.cleanString; 28 | auto testData = evaluation.currentValue.strValue.cleanString; 29 | 30 | if(!evaluation.isNegated) { 31 | auto missingValues = expectedPieces.filter!(a => !testData.canFind(a)).array; 32 | 33 | if(missingValues.length > 0) { 34 | addLifecycleMessage(evaluation, missingValues); 35 | try results ~= new ExpectedActualResult(createResultMessage(evaluation.expectedValue, expectedPieces), testData); 36 | catch(Exception e) { 37 | results ~= e.toResults; 38 | return results; 39 | } 40 | } 41 | } else { 42 | auto presentValues = expectedPieces.filter!(a => testData.canFind(a)).array; 43 | 44 | if(presentValues.length > 0) { 45 | string message = "to not contain "; 46 | 47 | if(presentValues.length > 1) { 48 | message ~= "any "; 49 | } 50 | 51 | message ~= evaluation.expectedValue.strValue; 52 | 53 | evaluation.message.addText(" "); 54 | 55 | if(presentValues.length == 1) { 56 | try evaluation.message.addValue(presentValues[0]); catch(Exception e) { 57 | evaluation.message.addText(" some value "); 58 | } 59 | 60 | evaluation.message.addText(" is present in "); 61 | } else { 62 | try evaluation.message.addValue(presentValues.to!string); catch(Exception e) { 63 | evaluation.message.addText(" some values "); 64 | } 65 | 66 | evaluation.message.addText(" are present in "); 67 | } 68 | 69 | evaluation.message.addValue(evaluation.currentValue.strValue); 70 | evaluation.message.addText("."); 71 | 72 | try results ~= new ExpectedActualResult(message, testData); 73 | catch(Exception e) { 74 | results ~= e.toResults; 75 | return results; 76 | } 77 | } 78 | } 79 | 80 | return results; 81 | } 82 | 83 | /// 84 | IResult[] arrayContain(ref Evaluation evaluation) @trusted nothrow { 85 | evaluation.message.addText("."); 86 | 87 | IResult[] results = []; 88 | 89 | auto expectedPieces = evaluation.expectedValue.proxyValue.toArray; 90 | auto testData = evaluation.currentValue.proxyValue.toArray; 91 | 92 | if(!evaluation.isNegated) { 93 | auto missingValues = expectedPieces.filter!(a => testData.filter!(b => b.isEqualTo(a)).empty).array; 94 | 95 | if(missingValues.length > 0) { 96 | addLifecycleMessage(evaluation, missingValues); 97 | try results ~= new ExpectedActualResult(createResultMessage(evaluation.expectedValue, expectedPieces), evaluation.currentValue.strValue); 98 | catch(Exception e) { 99 | results ~= e.toResults; 100 | return results; 101 | } 102 | } 103 | } else { 104 | auto presentValues = expectedPieces.filter!(a => !testData.filter!(b => b.isEqualTo(a)).empty).array; 105 | 106 | if(presentValues.length > 0) { 107 | addNegatedLifecycleMessage(evaluation, presentValues); 108 | try results ~= new ExpectedActualResult(createNegatedResultMessage(evaluation.expectedValue, expectedPieces), evaluation.currentValue.strValue); 109 | catch(Exception e) { 110 | results ~= e.toResults; 111 | return results; 112 | } 113 | } 114 | } 115 | 116 | return results; 117 | } 118 | 119 | /// 120 | IResult[] arrayContainOnly(ref Evaluation evaluation) @safe nothrow { 121 | evaluation.message.addText("."); 122 | 123 | IResult[] results = []; 124 | 125 | auto expectedPieces = evaluation.expectedValue.proxyValue.toArray; 126 | auto testData = evaluation.currentValue.proxyValue.toArray; 127 | 128 | auto comparison = ListComparison!EquableValue(testData, expectedPieces); 129 | 130 | EquableValue[] missing; 131 | EquableValue[] extra; 132 | EquableValue[] common; 133 | 134 | try { 135 | missing = comparison.missing; 136 | extra = comparison.extra; 137 | common = comparison.common; 138 | } catch(Exception e) { 139 | results ~= e.toResults; 140 | 141 | return results; 142 | } 143 | 144 | string strExtra = ""; 145 | string strMissing = ""; 146 | 147 | if(extra.length > 0) { 148 | strExtra = extra.niceJoin(evaluation.currentValue.typeName); 149 | } 150 | 151 | if(missing.length > 0) { 152 | strMissing = missing.niceJoin(evaluation.currentValue.typeName); 153 | } 154 | 155 | if(!evaluation.isNegated) { 156 | auto isSuccess = missing.length == 0 && extra.length == 0 && common.length == testData.length; 157 | 158 | if(!isSuccess) { 159 | try results ~= new ExpectedActualResult("", testData.niceJoin(evaluation.currentValue.typeName)); 160 | catch(Exception e) { 161 | results ~= e.toResults; 162 | return results; 163 | } 164 | 165 | try results ~= new ExtraMissingResult(strExtra, strMissing); 166 | catch(Exception e) { 167 | results ~= e.toResults; 168 | return results; 169 | } 170 | } 171 | } else { 172 | auto isSuccess = (missing.length != 0 || extra.length != 0) || common.length != testData.length; 173 | 174 | if(!isSuccess) { 175 | try results ~= new ExpectedActualResult("to not contain " ~ expectedPieces.niceJoin(evaluation.currentValue.typeName), testData.niceJoin(evaluation.currentValue.typeName)); 176 | catch(Exception e) { 177 | results ~= e.toResults; 178 | return results; 179 | } 180 | } 181 | } 182 | 183 | return results; 184 | } 185 | 186 | /// 187 | void addLifecycleMessage(ref Evaluation evaluation, string[] missingValues) @safe nothrow { 188 | evaluation.message.addText(" "); 189 | 190 | if(missingValues.length == 1) { 191 | try evaluation.message.addValue(missingValues[0]); catch(Exception) { 192 | evaluation.message.addText(" some value "); 193 | } 194 | 195 | evaluation.message.addText(" is missing from "); 196 | } else { 197 | try { 198 | evaluation.message.addValue(missingValues.niceJoin(evaluation.currentValue.typeName)); 199 | } catch(Exception) { 200 | evaluation.message.addText(" some values "); 201 | } 202 | 203 | evaluation.message.addText(" are missing from "); 204 | } 205 | 206 | evaluation.message.addValue(evaluation.currentValue.strValue); 207 | evaluation.message.addText("."); 208 | } 209 | 210 | /// 211 | void addLifecycleMessage(ref Evaluation evaluation, EquableValue[] missingValues) @safe nothrow { 212 | auto missing = missingValues.map!(a => a.getSerialized.cleanString).array; 213 | 214 | addLifecycleMessage(evaluation, missing); 215 | } 216 | 217 | /// 218 | void addNegatedLifecycleMessage(ref Evaluation evaluation, string[] presentValues) @safe nothrow { 219 | evaluation.message.addText(" "); 220 | 221 | if(presentValues.length == 1) { 222 | try evaluation.message.addValue(presentValues[0]); catch(Exception e) { 223 | evaluation.message.addText(" some value "); 224 | } 225 | 226 | evaluation.message.addText(" is present in "); 227 | } else { 228 | try evaluation.message.addValue(presentValues.niceJoin(evaluation.currentValue.typeName)); 229 | catch(Exception e) { 230 | evaluation.message.addText(" some values "); 231 | } 232 | 233 | evaluation.message.addText(" are present in "); 234 | } 235 | 236 | evaluation.message.addValue(evaluation.currentValue.strValue); 237 | evaluation.message.addText("."); 238 | } 239 | 240 | /// 241 | void addNegatedLifecycleMessage(ref Evaluation evaluation, EquableValue[] missingValues) @safe nothrow { 242 | auto missing = missingValues.map!(a => a.getSerialized).array; 243 | 244 | addNegatedLifecycleMessage(evaluation, missing); 245 | } 246 | 247 | string createResultMessage(ValueEvaluation expectedValue, string[] expectedPieces) @safe nothrow { 248 | string message = "to contain "; 249 | 250 | if(expectedPieces.length > 1) { 251 | message ~= "all "; 252 | } 253 | 254 | message ~= expectedValue.strValue; 255 | 256 | return message; 257 | } 258 | 259 | /// 260 | string createResultMessage(ValueEvaluation expectedValue, EquableValue[] missingValues) @safe nothrow { 261 | auto missing = missingValues.map!(a => a.getSerialized).array; 262 | 263 | return createResultMessage(expectedValue, missing); 264 | } 265 | 266 | string createNegatedResultMessage(ValueEvaluation expectedValue, string[] expectedPieces) @safe nothrow { 267 | string message = "to not contain "; 268 | 269 | if(expectedPieces.length > 1) { 270 | message ~= "any "; 271 | } 272 | 273 | message ~= expectedValue.strValue; 274 | 275 | return message; 276 | } 277 | 278 | /// 279 | string createNegatedResultMessage(ValueEvaluation expectedValue, EquableValue[] missingValues) @safe nothrow { 280 | auto missing = missingValues.map!(a => a.getSerialized).array; 281 | 282 | return createNegatedResultMessage(expectedValue, missing); 283 | } 284 | 285 | string niceJoin(string[] values, string typeName = "") @safe nothrow { 286 | string result = ""; 287 | 288 | try { 289 | result = values.to!string; 290 | 291 | if(!typeName.canFind("string")) { 292 | result = result.replace(`"`, ""); 293 | } 294 | } catch(Exception) {} 295 | 296 | return result; 297 | } 298 | 299 | string niceJoin(EquableValue[] values, string typeName = "") @safe nothrow { 300 | return values.map!(a => a.getSerialized.cleanString).array.niceJoin(typeName); 301 | } 302 | 303 | -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/endWith.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.endWith; 2 | 3 | import std.string; 4 | 5 | import fluentasserts.core.results; 6 | import fluentasserts.core.evaluation; 7 | import fluentasserts.core.serializers; 8 | 9 | import fluentasserts.core.lifecycle; 10 | 11 | version(unittest) { 12 | import fluentasserts.core.expect; 13 | } 14 | 15 | static immutable endWithDescription = "Tests that the tested string ends with the expected value."; 16 | 17 | /// 18 | IResult[] endWith(ref Evaluation evaluation) @safe nothrow { 19 | evaluation.message.addText("."); 20 | 21 | IResult[] results = []; 22 | auto current = evaluation.currentValue.strValue.cleanString; 23 | auto expected = evaluation.expectedValue.strValue.cleanString; 24 | 25 | long index = -1; 26 | 27 | try { 28 | index = current.lastIndexOf(expected); 29 | } catch(Exception) { } 30 | 31 | auto doesEndWith = index >= 0 && index == current.length - expected.length; 32 | 33 | if(evaluation.isNegated) { 34 | if(doesEndWith) { 35 | evaluation.message.addText(" "); 36 | evaluation.message.addValue(evaluation.currentValue.strValue); 37 | evaluation.message.addText(" ends with "); 38 | evaluation.message.addValue(evaluation.expectedValue.strValue); 39 | evaluation.message.addText("."); 40 | 41 | try results ~= new ExpectedActualResult("to not end with " ~ evaluation.expectedValue.strValue, evaluation.currentValue.strValue); 42 | catch(Exception e) {} 43 | } 44 | } else { 45 | if(!doesEndWith) { 46 | evaluation.message.addText(" "); 47 | evaluation.message.addValue(evaluation.currentValue.strValue); 48 | evaluation.message.addText(" does not end with "); 49 | evaluation.message.addValue(evaluation.expectedValue.strValue); 50 | evaluation.message.addText("."); 51 | 52 | try results ~= new ExpectedActualResult("to end with " ~ evaluation.expectedValue.strValue, evaluation.currentValue.strValue); 53 | catch(Exception e) {} 54 | } 55 | } 56 | 57 | return results; 58 | } 59 | -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/equal.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.equal; 2 | 3 | import fluentasserts.core.results; 4 | import fluentasserts.core.evaluation; 5 | 6 | import fluentasserts.core.lifecycle; 7 | import fluentasserts.core.message; 8 | 9 | version(unittest) { 10 | import fluentasserts.core.expect; 11 | } 12 | 13 | static immutable equalDescription = "Asserts that the target is strictly == equal to the given val."; 14 | 15 | static immutable isEqualTo = Message(Message.Type.info, " is equal to "); 16 | static immutable isNotEqualTo = Message(Message.Type.info, " is not equal to "); 17 | static immutable endSentence = Message(Message.Type.info, ". "); 18 | 19 | /// 20 | IResult[] equal(ref Evaluation evaluation) @safe nothrow { 21 | EvaluationResult evaluationResult; 22 | 23 | evaluation.message.add(endSentence); 24 | 25 | bool result = evaluation.currentValue.strValue == evaluation.expectedValue.strValue; 26 | 27 | if(!result && evaluation.currentValue.proxyValue !is null && evaluation.expectedValue.proxyValue !is null) { 28 | result = evaluation.currentValue.proxyValue.isEqualTo(evaluation.expectedValue.proxyValue); 29 | } 30 | 31 | if(evaluation.isNegated) { 32 | result = !result; 33 | } 34 | 35 | if(result) { 36 | return []; 37 | } 38 | 39 | IResult[] results = []; 40 | 41 | if(evaluation.currentValue.typeName != "bool") { 42 | evaluation.message.add(Message(Message.Type.value, evaluation.currentValue.strValue)); 43 | 44 | if(evaluation.isNegated) { 45 | evaluation.message.add(isEqualTo); 46 | } else { 47 | evaluation.message.add(isNotEqualTo); 48 | } 49 | 50 | evaluation.message.add(Message(Message.Type.value, evaluation.expectedValue.strValue)); 51 | evaluation.message.add(endSentence); 52 | 53 | evaluationResult.addDiff(evaluation.expectedValue.strValue, evaluation.currentValue.strValue); 54 | try results ~= new DiffResult(evaluation.expectedValue.strValue, evaluation.currentValue.strValue); catch(Exception) {} 55 | } 56 | 57 | evaluationResult.addExpected(evaluation.isNegated, evaluation.expectedValue.strValue); 58 | evaluationResult.addResult(evaluation.currentValue.strValue); 59 | 60 | return evaluationResult.toException; 61 | } 62 | -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/greaterOrEqualTo.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.greaterOrEqualTo; 2 | 3 | import fluentasserts.core.results; 4 | import fluentasserts.core.evaluation; 5 | 6 | import fluentasserts.core.lifecycle; 7 | 8 | import std.conv; 9 | import std.datetime; 10 | 11 | version(unittest) { 12 | import fluentasserts.core.expect; 13 | } 14 | 15 | static immutable greaterOrEqualToDescription = "Asserts that the tested value is greater or equal than the tested value. However, it's often best to assert that the target is equal to its expected value."; 16 | 17 | /// 18 | IResult[] greaterOrEqualTo(T)(ref Evaluation evaluation) @safe nothrow { 19 | evaluation.message.addText("."); 20 | 21 | T expectedValue; 22 | T currentValue; 23 | 24 | try { 25 | expectedValue = evaluation.expectedValue.strValue.to!T; 26 | currentValue = evaluation.currentValue.strValue.to!T; 27 | } catch(Exception e) { 28 | return [ new MessageResult("Can't convert the values to " ~ T.stringof) ]; 29 | } 30 | 31 | auto result = currentValue >= expectedValue; 32 | 33 | return greaterOrEqualToResults(result, evaluation.expectedValue.strValue, evaluation.currentValue.strValue, evaluation); 34 | } 35 | 36 | IResult[] greaterOrEqualToDuration(ref Evaluation evaluation) @safe nothrow { 37 | evaluation.message.addText("."); 38 | 39 | Duration expectedValue; 40 | Duration currentValue; 41 | string niceExpectedValue; 42 | string niceCurrentValue; 43 | 44 | try { 45 | expectedValue = dur!"nsecs"(evaluation.expectedValue.strValue.to!size_t); 46 | currentValue = dur!"nsecs"(evaluation.currentValue.strValue.to!size_t); 47 | 48 | niceExpectedValue = expectedValue.to!string; 49 | niceCurrentValue = currentValue.to!string; 50 | } catch(Exception e) { 51 | return [ new MessageResult("Can't convert the values to Duration") ]; 52 | } 53 | 54 | auto result = currentValue >= expectedValue; 55 | 56 | return greaterOrEqualToResults(result, niceExpectedValue, niceCurrentValue, evaluation); 57 | } 58 | 59 | IResult[] greaterOrEqualToSysTime(ref Evaluation evaluation) @safe nothrow { 60 | evaluation.message.addText("."); 61 | 62 | SysTime expectedValue; 63 | SysTime currentValue; 64 | string niceExpectedValue; 65 | string niceCurrentValue; 66 | 67 | try { 68 | expectedValue = SysTime.fromISOExtString(evaluation.expectedValue.strValue); 69 | currentValue = SysTime.fromISOExtString(evaluation.currentValue.strValue); 70 | } catch(Exception e) { 71 | return [ new MessageResult("Can't convert the values to SysTime") ]; 72 | } 73 | 74 | auto result = currentValue >= expectedValue; 75 | 76 | return greaterOrEqualToResults(result, evaluation.expectedValue.strValue, evaluation.currentValue.strValue, evaluation); 77 | } 78 | 79 | private IResult[] greaterOrEqualToResults(bool result, string niceExpectedValue, string niceCurrentValue, ref Evaluation evaluation) @safe nothrow { 80 | if(evaluation.isNegated) { 81 | result = !result; 82 | } 83 | 84 | if(result) { 85 | return []; 86 | } 87 | 88 | evaluation.message.addText(" "); 89 | evaluation.message.addValue(evaluation.currentValue.niceValue); 90 | 91 | IResult[] results = []; 92 | 93 | if(evaluation.isNegated) { 94 | evaluation.message.addText(" is greater or equal than "); 95 | results ~= new ExpectedActualResult("less than " ~ niceExpectedValue, niceCurrentValue); 96 | } else { 97 | evaluation.message.addText(" is less than "); 98 | results ~= new ExpectedActualResult("greater or equal than " ~ niceExpectedValue, niceCurrentValue); 99 | } 100 | 101 | evaluation.message.addValue(niceExpectedValue); 102 | evaluation.message.addText("."); 103 | 104 | return results; 105 | } 106 | -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/greaterThan.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.greaterThan; 2 | 3 | import fluentasserts.core.results; 4 | import fluentasserts.core.evaluation; 5 | 6 | import fluentasserts.core.lifecycle; 7 | 8 | import std.conv; 9 | import std.datetime; 10 | 11 | version(unittest) { 12 | import fluentasserts.core.expect; 13 | } 14 | 15 | static immutable greaterThanDescription = "Asserts that the tested value is greater than the tested value. However, it's often best to assert that the target is equal to its expected value."; 16 | 17 | /// 18 | IResult[] greaterThan(T)(ref Evaluation evaluation) @safe nothrow { 19 | evaluation.message.addText("."); 20 | 21 | T expectedValue; 22 | T currentValue; 23 | 24 | try { 25 | expectedValue = evaluation.expectedValue.strValue.to!T; 26 | currentValue = evaluation.currentValue.strValue.to!T; 27 | } catch(Exception e) { 28 | return [ new MessageResult("Can't convert the values to " ~ T.stringof) ]; 29 | } 30 | 31 | auto result = currentValue > expectedValue; 32 | 33 | return greaterThanResults(result, evaluation.expectedValue.strValue, evaluation.currentValue.strValue, evaluation); 34 | } 35 | 36 | /// 37 | IResult[] greaterThanDuration(ref Evaluation evaluation) @safe nothrow { 38 | evaluation.message.addText("."); 39 | 40 | Duration expectedValue; 41 | Duration currentValue; 42 | string niceExpectedValue; 43 | string niceCurrentValue; 44 | 45 | try { 46 | expectedValue = dur!"nsecs"(evaluation.expectedValue.strValue.to!size_t); 47 | currentValue = dur!"nsecs"(evaluation.currentValue.strValue.to!size_t); 48 | 49 | niceExpectedValue = expectedValue.to!string; 50 | niceCurrentValue = currentValue.to!string; 51 | } catch(Exception e) { 52 | return [ new MessageResult("Can't convert the values to Duration") ]; 53 | } 54 | 55 | auto result = currentValue > expectedValue; 56 | 57 | return greaterThanResults(result, niceExpectedValue, niceCurrentValue, evaluation); 58 | } 59 | 60 | /// 61 | IResult[] greaterThanSysTime(ref Evaluation evaluation) @safe nothrow { 62 | evaluation.message.addText("."); 63 | 64 | SysTime expectedValue; 65 | SysTime currentValue; 66 | string niceExpectedValue; 67 | string niceCurrentValue; 68 | 69 | try { 70 | expectedValue = SysTime.fromISOExtString(evaluation.expectedValue.strValue); 71 | currentValue = SysTime.fromISOExtString(evaluation.currentValue.strValue); 72 | } catch(Exception e) { 73 | return [ new MessageResult("Can't convert the values to SysTime") ]; 74 | } 75 | 76 | auto result = currentValue > expectedValue; 77 | 78 | return greaterThanResults(result, evaluation.expectedValue.strValue, evaluation.currentValue.strValue, evaluation); 79 | } 80 | 81 | private IResult[] greaterThanResults(bool result, string niceExpectedValue, string niceCurrentValue, ref Evaluation evaluation) @safe nothrow { 82 | if(evaluation.isNegated) { 83 | result = !result; 84 | } 85 | 86 | if(result) { 87 | return []; 88 | } 89 | 90 | evaluation.message.addText(" "); 91 | evaluation.message.addValue(evaluation.currentValue.niceValue); 92 | 93 | IResult[] results = []; 94 | 95 | if(evaluation.isNegated) { 96 | evaluation.message.addText(" is greater than "); 97 | results ~= new ExpectedActualResult("less than or equal to " ~ niceExpectedValue, niceCurrentValue); 98 | } else { 99 | evaluation.message.addText(" is less than or equal to "); 100 | results ~= new ExpectedActualResult("greater than " ~ niceExpectedValue, niceCurrentValue); 101 | } 102 | 103 | evaluation.message.addValue(niceExpectedValue); 104 | evaluation.message.addText("."); 105 | 106 | return results; 107 | } 108 | -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/instanceOf.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.instanceOf; 2 | 3 | import fluentasserts.core.results; 4 | import fluentasserts.core.evaluation; 5 | 6 | import fluentasserts.core.lifecycle; 7 | 8 | import std.conv; 9 | import std.datetime; 10 | import std.algorithm; 11 | 12 | version(unittest) { 13 | import fluentasserts.core.expect; 14 | } 15 | 16 | static immutable instanceOfDescription = "Asserts that the tested value is related to a type."; 17 | 18 | /// 19 | IResult[] instanceOf(ref Evaluation evaluation) @safe nothrow { 20 | string expectedType = evaluation.expectedValue.strValue[1 .. $-1]; 21 | string currentType = evaluation.currentValue.typeNames[0]; 22 | 23 | evaluation.message.addText(". "); 24 | 25 | auto existingTypes = findAmong(evaluation.currentValue.typeNames, [expectedType]); 26 | 27 | import std.stdio; 28 | 29 | auto isExpected = existingTypes.length > 0; 30 | 31 | if(evaluation.isNegated) { 32 | isExpected = !isExpected; 33 | } 34 | 35 | IResult[] results = []; 36 | 37 | if(!isExpected) { 38 | evaluation.message.addValue(evaluation.currentValue.strValue); 39 | evaluation.message.addText(" is instance of "); 40 | evaluation.message.addValue(currentType); 41 | evaluation.message.addText("."); 42 | } 43 | 44 | if(!isExpected && !evaluation.isNegated) { 45 | try results ~= new ExpectedActualResult("typeof " ~ expectedType, "typeof " ~ currentType); catch(Exception) {} 46 | } 47 | 48 | if(!isExpected && evaluation.isNegated) { 49 | try results ~= new ExpectedActualResult("not typeof " ~ expectedType, "typeof " ~ currentType); catch(Exception) {} 50 | } 51 | 52 | return results; 53 | } -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/lessOrEqualTo.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.lessOrEqualTo; 2 | 3 | import fluentasserts.core.results; 4 | import fluentasserts.core.evaluation; 5 | 6 | import fluentasserts.core.lifecycle; 7 | 8 | import std.conv; 9 | import std.datetime; 10 | 11 | version(unittest) { 12 | import fluentasserts.core.expect; 13 | } 14 | 15 | static immutable lessOrEqualToDescription = "Asserts that the tested value is less or equal than the tested value. However, it's often best to assert that the target is equal to its expected value."; 16 | 17 | /// 18 | IResult[] lessOrEqualTo(T)(ref Evaluation evaluation) @safe nothrow { 19 | evaluation.message.addText("."); 20 | 21 | T expectedValue; 22 | T currentValue; 23 | 24 | try { 25 | expectedValue = evaluation.expectedValue.strValue.to!T; 26 | currentValue = evaluation.currentValue.strValue.to!T; 27 | } catch(Exception e) { 28 | return [ new MessageResult("Can't convert the values to " ~ T.stringof) ]; 29 | } 30 | 31 | auto result = currentValue <= expectedValue; 32 | 33 | if(evaluation.isNegated) { 34 | result = !result; 35 | } 36 | 37 | if(result) { 38 | return []; 39 | } 40 | 41 | evaluation.message.addText(" "); 42 | evaluation.message.addValue(evaluation.currentValue.niceValue); 43 | 44 | IResult[] results = []; 45 | 46 | if(evaluation.isNegated) { 47 | evaluation.message.addText(" is less or equal to "); 48 | results ~= new ExpectedActualResult("greater than " ~ evaluation.expectedValue.niceValue, evaluation.currentValue.niceValue); 49 | } else { 50 | evaluation.message.addText(" is greater than "); 51 | results ~= new ExpectedActualResult("less or equal to " ~ evaluation.expectedValue.niceValue, evaluation.currentValue.niceValue); 52 | } 53 | 54 | evaluation.message.addValue(evaluation.expectedValue.niceValue); 55 | evaluation.message.addText("."); 56 | 57 | return results; 58 | } 59 | -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/lessThan.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.lessThan; 2 | 3 | import fluentasserts.core.results; 4 | import fluentasserts.core.evaluation; 5 | 6 | import fluentasserts.core.lifecycle; 7 | 8 | import std.conv; 9 | import std.datetime; 10 | 11 | version(unittest) { 12 | import fluentasserts.core.expect; 13 | } 14 | 15 | static immutable lessThanDescription = "Asserts that the tested value is less than the tested value. However, it's often best to assert that the target is equal to its expected value."; 16 | 17 | /// 18 | IResult[] lessThan(T)(ref Evaluation evaluation) @safe nothrow { 19 | evaluation.message.addText("."); 20 | 21 | T expectedValue; 22 | T currentValue; 23 | 24 | try { 25 | expectedValue = evaluation.expectedValue.strValue.to!T; 26 | currentValue = evaluation.currentValue.strValue.to!T; 27 | } catch(Exception e) { 28 | return [ new MessageResult("Can't convert the values to " ~ T.stringof) ]; 29 | } 30 | 31 | auto result = currentValue < expectedValue; 32 | 33 | return lessThanResults(result, evaluation.expectedValue.strValue, evaluation.currentValue.strValue, evaluation); 34 | } 35 | 36 | /// 37 | IResult[] lessThanDuration(ref Evaluation evaluation) @safe nothrow { 38 | evaluation.message.addText("."); 39 | 40 | Duration expectedValue; 41 | Duration currentValue; 42 | string niceExpectedValue; 43 | string niceCurrentValue; 44 | 45 | try { 46 | expectedValue = dur!"nsecs"(evaluation.expectedValue.strValue.to!size_t); 47 | currentValue = dur!"nsecs"(evaluation.currentValue.strValue.to!size_t); 48 | 49 | niceExpectedValue = expectedValue.to!string; 50 | niceCurrentValue = currentValue.to!string; 51 | } catch(Exception e) { 52 | return [ new MessageResult("Can't convert the values to Duration") ]; 53 | } 54 | 55 | auto result = currentValue < expectedValue; 56 | 57 | return lessThanResults(result, niceExpectedValue, niceCurrentValue, evaluation); 58 | } 59 | 60 | /// 61 | IResult[] lessThanSysTime(ref Evaluation evaluation) @safe nothrow { 62 | evaluation.message.addText("."); 63 | 64 | SysTime expectedValue; 65 | SysTime currentValue; 66 | string niceExpectedValue; 67 | string niceCurrentValue; 68 | 69 | try { 70 | expectedValue = SysTime.fromISOExtString(evaluation.expectedValue.strValue); 71 | currentValue = SysTime.fromISOExtString(evaluation.currentValue.strValue); 72 | } catch(Exception e) { 73 | return [ new MessageResult("Can't convert the values to SysTime") ]; 74 | } 75 | 76 | auto result = currentValue < expectedValue; 77 | 78 | return lessThanResults(result, evaluation.expectedValue.strValue, evaluation.currentValue.strValue, evaluation); 79 | } 80 | 81 | private IResult[] lessThanResults(bool result, string niceExpectedValue, string niceCurrentValue, ref Evaluation evaluation) @safe nothrow { 82 | if(evaluation.isNegated) { 83 | result = !result; 84 | } 85 | 86 | if(result) { 87 | return []; 88 | } 89 | 90 | evaluation.message.addText(" "); 91 | evaluation.message.addValue(evaluation.currentValue.niceValue); 92 | 93 | IResult[] results = []; 94 | 95 | if(evaluation.isNegated) { 96 | evaluation.message.addText(" is less than "); 97 | results ~= new ExpectedActualResult("greater than or equal to " ~ niceExpectedValue, niceCurrentValue); 98 | } else { 99 | evaluation.message.addText(" is greater than or equal to "); 100 | results ~= new ExpectedActualResult("less than " ~ niceExpectedValue, niceCurrentValue); 101 | } 102 | 103 | evaluation.message.addValue(niceExpectedValue); 104 | evaluation.message.addText("."); 105 | 106 | return results; 107 | } -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/registry.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.registry; 2 | 3 | import fluentasserts.core.results; 4 | import fluentasserts.core.evaluation; 5 | 6 | import std.functional; 7 | import std.string; 8 | import std.array; 9 | import std.algorithm; 10 | 11 | /// Delegate type that can handle asserts 12 | alias Operation = IResult[] delegate(ref Evaluation) @safe nothrow; 13 | 14 | /// ditto 15 | alias OperationFunc = IResult[] delegate(ref Evaluation) @safe nothrow; 16 | 17 | 18 | struct OperationPair { 19 | string valueType; 20 | string expectedValueType; 21 | } 22 | 23 | /// 24 | class Registry { 25 | /// Global instance for the assert operations 26 | static Registry instance; 27 | 28 | private { 29 | Operation[string] operations; 30 | OperationPair[][string] pairs; 31 | string[string] descriptions; 32 | } 33 | 34 | /// Register a new assert operation 35 | Registry register(T, U)(string name, Operation operation) { 36 | foreach(valueType; extractTypes!T) { 37 | foreach(expectedValueType; extractTypes!U) { 38 | register(valueType, expectedValueType, name, operation); 39 | } 40 | } 41 | 42 | return this; 43 | } 44 | 45 | /// ditto 46 | Registry register(T, U)(string name, IResult[] function(ref Evaluation) @safe nothrow operation) { 47 | const operationDelegate = operation.toDelegate; 48 | return this.register!(T, U)(name, operationDelegate); 49 | } 50 | 51 | /// ditto 52 | Registry register(string valueType, string expectedValueType, string name, Operation operation) { 53 | string key = valueType ~ "." ~ expectedValueType ~ "." ~ name; 54 | 55 | operations[key] = operation; 56 | pairs[name] ~= OperationPair(valueType, expectedValueType); 57 | 58 | return this; 59 | } 60 | 61 | /// ditto 62 | Registry register(string valueType, string expectedValueType, string name, IResult[] function(ref Evaluation) @safe nothrow operation) { 63 | return this.register(valueType, expectedValueType, name, operation.toDelegate); 64 | } 65 | 66 | /// Get an operation function 67 | Operation get(string valueType, string expectedValueType, string name) @safe nothrow { 68 | assert(valueType != "", "The value type is not set!"); 69 | assert(name != "", "The operation name is not set!"); 70 | 71 | auto genericKeys = [valueType ~ "." ~ expectedValueType ~ "." ~ name] ~ generalizeKey(valueType, expectedValueType, name); 72 | string matchedKey; 73 | 74 | foreach(key; genericKeys) { 75 | if(key in operations) { 76 | matchedKey = key; 77 | break; 78 | } 79 | } 80 | 81 | assert(matchedKey != "", "There are no matching assert operations. Register any of `" ~ genericKeys.join("`, `") ~ "` to perform this assert."); 82 | 83 | return operations[matchedKey]; 84 | } 85 | 86 | /// 87 | IResult[] handle(ref Evaluation evaluation) @safe nothrow { 88 | if(evaluation.operationName == "" || evaluation.operationName == "to" || evaluation.operationName == "should") { 89 | return []; 90 | } 91 | 92 | auto operation = this.get( 93 | evaluation.currentValue.typeName, 94 | evaluation.expectedValue.typeName, 95 | evaluation.operationName); 96 | 97 | return operation(evaluation); 98 | } 99 | 100 | /// 101 | void describe(string name, string text) { 102 | descriptions[name] = text; 103 | } 104 | 105 | /// 106 | string describe(string name) { 107 | if(name !in descriptions) { 108 | return ""; 109 | } 110 | 111 | return descriptions[name]; 112 | } 113 | 114 | /// 115 | OperationPair[] bindingsForName(string name) { 116 | return pairs[name]; 117 | } 118 | 119 | /// 120 | string[] registeredOperations() { 121 | return operations.keys 122 | .map!(a => a.split(".")) 123 | .map!(a => a[a.length - 1]) 124 | .array 125 | .sort 126 | .uniq 127 | .array; 128 | } 129 | 130 | /// 131 | string docs() { 132 | string result = ""; 133 | 134 | string[] operationNames = registeredOperations 135 | .map!(a => "- [" ~ a ~ "](api/" ~ a ~ ".md)") 136 | .array; 137 | 138 | return operationNames.join("\n"); 139 | } 140 | } 141 | 142 | /// It generates a list of md links for docs 143 | unittest { 144 | import std.datetime; 145 | import fluentasserts.core.operations.equal; 146 | import fluentasserts.core.operations.lessThan; 147 | 148 | auto instance = new Registry(); 149 | 150 | instance.register("*", "*", "equal", &equal); 151 | instance.register!(Duration, Duration)("lessThan", &lessThanDuration); 152 | 153 | instance.docs.should.equal("- [equal](api/equal.md)\n" ~ "- [lessThan](api/lessThan.md)"); 154 | } 155 | 156 | string[] generalizeKey(string valueType, string expectedValueType, string name) @safe nothrow { 157 | string[] results; 158 | 159 | foreach (string generalizedValueType; generalizeType(valueType)) { 160 | foreach (string generalizedExpectedValueType; generalizeType(expectedValueType)) { 161 | results ~= generalizedValueType ~ "." ~ generalizedExpectedValueType ~ "." ~ name; 162 | } 163 | } 164 | 165 | return results; 166 | } 167 | 168 | string[] generalizeType(string typeName) @safe nothrow { 169 | auto pos = typeName.indexOf("["); 170 | if(pos == -1) { 171 | return ["*"]; 172 | } 173 | 174 | string[] results = []; 175 | 176 | const pieces = typeName.split("["); 177 | 178 | string arrayType; 179 | bool isHashMap; 180 | int index = 0; 181 | int diff = 0; 182 | 183 | foreach (ch; typeName[pos..$]) { 184 | diff++; 185 | if(ch == '[') { 186 | index++; 187 | } 188 | 189 | if(ch == ']') { 190 | index--; 191 | } 192 | 193 | if(index == 0 && diff == 2) { 194 | arrayType ~= "[]"; 195 | } 196 | 197 | if(index == 0 && diff != 2) { 198 | arrayType ~= "[*]"; 199 | isHashMap = true; 200 | } 201 | 202 | if(index == 0) { 203 | diff = 0; 204 | } 205 | } 206 | 207 | if(isHashMap) { 208 | results ~= "*" ~ typeName[pos..$]; 209 | results ~= pieces[0] ~ arrayType; 210 | } 211 | 212 | results ~= "*" ~ arrayType; 213 | 214 | return results; 215 | } 216 | 217 | version(unittest) { 218 | import fluentasserts.core.base; 219 | } 220 | 221 | /// It can generalize an int 222 | unittest { 223 | generalizeType("int").should.equal(["*"]); 224 | } 225 | 226 | /// It can generalize a list 227 | unittest { 228 | generalizeType("int[]").should.equal(["*[]"]); 229 | } 230 | 231 | /// It can generalize a list of lists 232 | unittest { 233 | generalizeType("int[][]").should.equal(["*[][]"]); 234 | } 235 | 236 | /// It can generalize an assoc array 237 | unittest { 238 | generalizeType("int[int]").should.equal(["*[int]", "int[*]", "*[*]"]); 239 | } 240 | 241 | /// It can generalize a combination of assoc arrays and lists 242 | unittest { 243 | generalizeType("int[int][][string][]").should.equal(["*[int][][string][]", "int[*][][*][]", "*[*][][*][]"]); 244 | } 245 | 246 | /// It can generalize an assoc array with a key list 247 | unittest { 248 | generalizeType("int[int[]]").should.equal(["*[int[]]", "int[*]", "*[*]"]); 249 | } 250 | -------------------------------------------------------------------------------- /source/fluentasserts/core/operations/startWith.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.operations.startWith; 2 | 3 | import std.string; 4 | 5 | import fluentasserts.core.results; 6 | import fluentasserts.core.evaluation; 7 | import fluentasserts.core.serializers; 8 | 9 | import fluentasserts.core.lifecycle; 10 | 11 | version(unittest) { 12 | import fluentasserts.core.expect; 13 | } 14 | 15 | static immutable startWithDescription = "Tests that the tested string starts with the expected value."; 16 | 17 | /// 18 | IResult[] startWith(ref Evaluation evaluation) @safe nothrow { 19 | evaluation.message.addText("."); 20 | 21 | IResult[] results = []; 22 | 23 | auto index = evaluation.currentValue.strValue.cleanString.indexOf(evaluation.expectedValue.strValue.cleanString); 24 | auto doesStartWith = index == 0; 25 | 26 | if(evaluation.isNegated) { 27 | if(doesStartWith) { 28 | evaluation.message.addText(" "); 29 | evaluation.message.addValue(evaluation.currentValue.strValue); 30 | evaluation.message.addText(" starts with "); 31 | evaluation.message.addValue(evaluation.expectedValue.strValue); 32 | evaluation.message.addText("."); 33 | 34 | try results ~= new ExpectedActualResult("to not start with " ~ evaluation.expectedValue.strValue, evaluation.currentValue.strValue); 35 | catch(Exception e) {} 36 | } 37 | } else { 38 | if(!doesStartWith) { 39 | evaluation.message.addText(" "); 40 | evaluation.message.addValue(evaluation.currentValue.strValue); 41 | evaluation.message.addText(" does not start with "); 42 | evaluation.message.addValue(evaluation.expectedValue.strValue); 43 | evaluation.message.addText("."); 44 | 45 | try results ~= new ExpectedActualResult("to start with " ~ evaluation.expectedValue.strValue, evaluation.currentValue.strValue); 46 | catch(Exception e) {} 47 | } 48 | } 49 | 50 | return results; 51 | } 52 | -------------------------------------------------------------------------------- /source/fluentasserts/core/string.d: -------------------------------------------------------------------------------- 1 | module fluentasserts.core.string; 2 | 3 | public import fluentasserts.core.base; 4 | import fluentasserts.core.results; 5 | 6 | import std.string; 7 | import std.conv; 8 | import std.algorithm; 9 | import std.array; 10 | 11 | /// When there is a lazy string that throws an it should throw that exception 12 | unittest { 13 | string someLazyString() { 14 | throw new Exception("This is it."); 15 | } 16 | 17 | ({ 18 | someLazyString.should.equal(""); 19 | }).should.throwAnyException.withMessage("This is it."); 20 | 21 | ({ 22 | someLazyString.should.contain(""); 23 | }).should.throwAnyException.withMessage("This is it."); 24 | 25 | ({ 26 | someLazyString.should.contain([""]); 27 | }).should.throwAnyException.withMessage("This is it."); 28 | 29 | ({ 30 | someLazyString.should.contain(' '); 31 | }).should.throwAnyException.withMessage("This is it."); 32 | 33 | ({ 34 | someLazyString.should.startWith(" "); 35 | }).should.throwAnyException.withMessage("This is it."); 36 | 37 | ({ 38 | someLazyString.should.endWith(" "); 39 | }).should.throwAnyException.withMessage("This is it."); 40 | } 41 | 42 | @("string startWith") 43 | unittest { 44 | ({ 45 | "test string".should.startWith("test"); 46 | }).should.not.throwAnyException; 47 | 48 | auto msg = ({ 49 | "test string".should.startWith("other"); 50 | }).should.throwException!TestException.msg; 51 | 52 | msg.split("\n")[0].should.contain(`"test string" does not start with "other"`); 53 | msg.split("\n")[2].strip.should.equal(`Expected:to start with "other"`); 54 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 55 | 56 | ({ 57 | "test string".should.not.startWith("other"); 58 | }).should.not.throwAnyException; 59 | 60 | msg = ({ 61 | "test string".should.not.startWith("test"); 62 | }).should.throwException!TestException.msg; 63 | 64 | msg.split("\n")[0].should.contain(`"test string" starts with "test"`); 65 | msg.split("\n")[2].strip.should.equal(`Expected:to not start with "test"`); 66 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 67 | 68 | ({ 69 | "test string".should.startWith('t'); 70 | }).should.not.throwAnyException; 71 | 72 | msg = ({ 73 | "test string".should.startWith('o'); 74 | }).should.throwException!TestException.msg; 75 | 76 | msg.split("\n")[0].should.contain(`"test string" does not start with 'o'`); 77 | msg.split("\n")[2].strip.should.equal("Expected:to start with 'o'"); 78 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 79 | 80 | ({ 81 | "test string".should.not.startWith('o'); 82 | }).should.not.throwAnyException; 83 | 84 | msg = ({ 85 | "test string".should.not.startWith('t'); 86 | }).should.throwException!TestException.msg; 87 | 88 | msg.split("\n")[0].should.contain(`"test string" starts with 't'`); 89 | msg.split("\n")[2].strip.should.equal(`Expected:to not start with 't'`); 90 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 91 | } 92 | 93 | @("string endWith") 94 | unittest { 95 | ({ 96 | "test string".should.endWith("string"); 97 | }).should.not.throwAnyException; 98 | 99 | auto msg = ({ 100 | "test string".should.endWith("other"); 101 | }).should.throwException!TestException.msg; 102 | 103 | msg.split("\n")[0].should.contain(`"test string" does not end with "other"`); 104 | msg.split("\n")[2].strip.should.equal(`Expected:to end with "other"`); 105 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 106 | 107 | ({ 108 | "test string".should.not.endWith("other"); 109 | }).should.not.throwAnyException; 110 | 111 | msg = ({ 112 | "test string".should.not.endWith("string"); 113 | }).should.throwException!TestException.msg; 114 | 115 | msg.split("\n")[0].should.equal(`"test string" should not end with "string". "test string" ends with "string".`); 116 | msg.split("\n")[2].strip.should.equal(`Expected:to not end with "string"`); 117 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 118 | 119 | ({ 120 | "test string".should.endWith('g'); 121 | }).should.not.throwAnyException; 122 | 123 | msg = ({ 124 | "test string".should.endWith('t'); 125 | }).should.throwException!TestException.msg; 126 | 127 | msg.split("\n")[0].should.contain(`"test string" does not end with 't'`); 128 | msg.split("\n")[2].strip.should.equal("Expected:to end with 't'"); 129 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 130 | 131 | ({ 132 | "test string".should.not.endWith('w'); 133 | }).should.not.throwAnyException; 134 | 135 | msg = ({ 136 | "test string".should.not.endWith('g'); 137 | }).should.throwException!TestException.msg; 138 | 139 | msg.split("\n")[0].should.contain(`"test string" ends with 'g'`); 140 | msg.split("\n")[2].strip.should.equal("Expected:to not end with 'g'"); 141 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 142 | } 143 | 144 | @("string contain") 145 | unittest { 146 | ({ 147 | "test string".should.contain(["string", "test"]); 148 | "test string".should.not.contain(["other", "message"]); 149 | }).should.not.throwAnyException; 150 | 151 | ({ 152 | "test string".should.contain("string"); 153 | "test string".should.not.contain("other"); 154 | }).should.not.throwAnyException; 155 | 156 | ({ 157 | "test string".should.contain('s'); 158 | "test string".should.not.contain('z'); 159 | }).should.not.throwAnyException; 160 | 161 | auto msg = ({ 162 | "test string".should.contain(["other", "message"]); 163 | }).should.throwException!TestException.msg; 164 | 165 | msg.split("\n")[0].should.equal(`"test string" should contain ["other", "message"]. ["other", "message"] are missing from "test string".`); 166 | msg.split("\n")[2].strip.should.equal(`Expected:to contain all ["other", "message"]`); 167 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 168 | 169 | msg = ({ 170 | "test string".should.not.contain(["test", "string"]); 171 | }).should.throwException!TestException.msg; 172 | 173 | msg.split("\n")[0].should.equal(`"test string" should not contain ["test", "string"]. ["test", "string"] are present in "test string".`); 174 | msg.split("\n")[2].strip.should.equal(`Expected:to not contain any ["test", "string"]`); 175 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 176 | 177 | msg = ({ 178 | "test string".should.contain("other"); 179 | }).should.throwException!TestException.msg; 180 | 181 | msg.split("\n")[0].should.equal(`"test string" should contain "other". other is missing from "test string".`); 182 | msg.split("\n")[2].strip.should.equal(`Expected:to contain "other"`); 183 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 184 | 185 | msg = ({ 186 | "test string".should.not.contain("test"); 187 | }).should.throwException!TestException.msg; 188 | 189 | msg.split("\n")[0].should.equal(`"test string" should not contain "test". test is present in "test string".`); 190 | msg.split("\n")[2].strip.should.equal(`Expected:to not contain "test"`); 191 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 192 | 193 | msg = ({ 194 | "test string".should.contain('o'); 195 | }).should.throwException!TestException.msg; 196 | 197 | msg.split("\n")[0].should.contain(`o is missing from "test string"`); 198 | msg.split("\n")[2].strip.should.equal("Expected:to contain 'o'"); 199 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 200 | 201 | msg = ({ 202 | "test string".should.not.contain('t'); 203 | }).should.throwException!TestException.msg; 204 | 205 | msg.split("\n")[0].should.equal(`"test string" should not contain 't'. t is present in "test string".`); 206 | msg.split("\n")[2].strip.should.equal("Expected:to not contain 't'"); 207 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 208 | } 209 | 210 | @("string equal") 211 | unittest { 212 | ({ 213 | "test string".should.equal("test string"); 214 | }).should.not.throwAnyException; 215 | 216 | ({ 217 | "test string".should.not.equal("test"); 218 | }).should.not.throwAnyException; 219 | 220 | auto msg = ({ 221 | "test string".should.equal("test"); 222 | }).should.throwException!TestException.msg; 223 | 224 | msg.split("\n")[0].should.equal(`"test string" should equal "test". "test string" is not equal to "test". `); 225 | 226 | msg = ({ 227 | "test string".should.not.equal("test string"); 228 | }).should.throwException!TestException.msg; 229 | 230 | msg.split("\n")[0].should.equal(`"test string" should not equal "test string". "test string" is equal to "test string". `); 231 | } 232 | 233 | /// it shows null chars in the diff 234 | unittest { 235 | string msg; 236 | 237 | try { 238 | ubyte[] data = [115, 111, 109, 101, 32, 100, 97, 116, 97, 0, 0]; 239 | data.assumeUTF.to!string.should.equal("some data"); 240 | } catch(TestException e) { 241 | msg = e.message.to!string; 242 | } 243 | 244 | msg.should.contain(`Actual:"some data\0\0"`); 245 | msg.should.contain(`data.assumeUTF.to!string should equal "some data". "some data\0\0" is not equal to "some data".`); 246 | msg.should.contain(`some data[+\0\0]`); 247 | } 248 | 249 | /// should throw exceptions for delegates that return basic types 250 | unittest { 251 | string value() { 252 | throw new Exception("not implemented"); 253 | } 254 | 255 | value().should.throwAnyException.withMessage.equal("not implemented"); 256 | 257 | string noException() { return null; } 258 | bool thrown; 259 | 260 | try { 261 | noException.should.throwAnyException; 262 | } catch(TestException e) { 263 | e.msg.should.startWith("noException should throw any exception. No exception was thrown."); 264 | thrown = true; 265 | } 266 | 267 | thrown.should.equal(true); 268 | } 269 | 270 | @("const string equal") 271 | unittest { 272 | const string constValue = "test string"; 273 | immutable string immutableValue = "test string"; 274 | 275 | constValue.should.equal("test string"); 276 | immutableValue.should.equal("test string"); 277 | 278 | "test string".should.equal(constValue); 279 | "test string".should.equal(immutableValue); 280 | } 281 | -------------------------------------------------------------------------------- /source/updateDocs.d: -------------------------------------------------------------------------------- 1 | module updateDocs; 2 | 3 | import fluentasserts.core.operations.registry; 4 | import std.stdio; 5 | import std.file; 6 | import std.path; 7 | import std.array; 8 | import std.algorithm; 9 | import std.string; 10 | 11 | /// updating the built in operations in readme.md file 12 | unittest { 13 | auto content = readText("README.md").split("#"); 14 | 15 | foreach(ref section; content) { 16 | if(!section.startsWith(" Built in operations\n")) { 17 | continue; 18 | } 19 | 20 | section = " Built in operations\n\n"; 21 | 22 | section ~= Registry.instance.docs ~ "\n\n"; 23 | } 24 | 25 | std.file.write("README.md", content.join("#")); 26 | } 27 | 28 | /// updating the operations md files 29 | unittest { 30 | foreach(operation; Registry.instance.registeredOperations) { 31 | string content = "# The `" ~ operation ~ "` operation\n\n"; 32 | content ~= "[up](../README.md)\n\n"; 33 | 34 | content ~= Registry.instance.describe(operation) ~ "\n\n"; 35 | 36 | content ~= "Works with:\n" ; 37 | content ~= Registry.instance.bindingsForName(operation) 38 | .map!(a => " - expect(`" ~ a.valueType ~ "`).[to].[be]." ~ operation ~ "(`" ~ a.expectedValueType ~ "`)") 39 | .join("\n"); 40 | 41 | content ~= "\n"; 42 | 43 | std.file.write(buildPath("api", operation ~ ".md"), content); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/class.d: -------------------------------------------------------------------------------- 1 | module some.module; 2 | 3 | class Foo { 4 | 5 | this() { 6 | ///... 7 | } 8 | 9 | void bar() { 10 | assert(false); 11 | } 12 | 13 | void bar2() { 14 | enforce(false); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/example.txt: -------------------------------------------------------------------------------- 1 | line 1 2 | line 2 3 | line 3 4 | line 4 5 | line 5 6 | line 6 7 | line 7 8 | line 8 9 | line 9 10 | line 10 11 | line 11 12 | line 12 13 | line 13 14 | line 14 15 | line 15 16 | line 16 17 | line 17 18 | line 18 19 | -------------------------------------------------------------------------------- /test/operations/approximately.d: -------------------------------------------------------------------------------- 1 | module test.operations.approximately; 2 | 3 | import fluentasserts.core.expect; 4 | import fluent.asserts; 5 | 6 | import trial.discovery.spec; 7 | 8 | import std.string; 9 | import std.conv; 10 | import std.meta; 11 | import std.algorithm; 12 | import std.range; 13 | 14 | alias s = Spec!({ 15 | alias IntTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong); 16 | 17 | alias FPTypes = AliasSeq!(float, double, real); 18 | 19 | static foreach(Type; FPTypes) { 20 | describe("using floats casted to " ~ Type.stringof, { 21 | Type testValue; 22 | 23 | before({ 24 | testValue = cast(Type) 10f/3f; 25 | }); 26 | 27 | it("should check for valid values", { 28 | testValue.should.be.approximately(3, 0.34); 29 | [testValue].should.be.approximately([3], 0.34); 30 | }); 31 | 32 | it("should check for invalid values", { 33 | testValue.should.not.be.approximately(3, 0.24); 34 | [testValue].should.not.be.approximately([3], 0.24); 35 | }); 36 | 37 | it("should not compare a string with a number", { 38 | auto msg = ({ 39 | "".should.be.approximately(3, 0.34); 40 | }).should.throwSomething.msg; 41 | 42 | msg.split("\n")[0].should.equal("There are no matching assert operations. Register any of `string.int.approximately`, `*.*.approximately` to perform this assert."); 43 | }); 44 | }); 45 | 46 | describe("using " ~ Type.stringof ~ " values", { 47 | Type testValue; 48 | 49 | before({ 50 | testValue = cast(Type) 0.351; 51 | }); 52 | 53 | it("should check approximately compare two numbers", { 54 | expect(testValue).to.be.approximately(0.35, 0.01); 55 | }); 56 | 57 | it("should check approximately with a delta of 0.00001", { 58 | expect(testValue).to.not.be.approximately(0.35, 0.00001); 59 | }); 60 | 61 | it("should check approximately with a delta of 0.001", { 62 | expect(testValue).to.not.be.approximately(0.35, 0.001); 63 | }); 64 | 65 | it("should show a detailed error message when two numbers should be approximately equal but they are not", { 66 | auto msg = ({ 67 | expect(testValue).to.be.approximately(0.35, 0.0001); 68 | }).should.throwException!TestException.msg; 69 | 70 | msg.should.contain("Expected:0.35±0.0001"); 71 | msg.should.contain("Actual:0.351"); 72 | msg.should.not.contain("Missing:"); 73 | }); 74 | 75 | it("should show a detailed error message when two numbers are approximately equal but they should not", { 76 | auto msg = ({ 77 | expect(testValue).to.not.be.approximately(testValue, 0.0001); 78 | }).should.throwException!TestException.msg; 79 | 80 | msg.should.contain("Expected:not " ~ testValue.to!string ~ "±0.0001"); 81 | }); 82 | }); 83 | 84 | describe("using " ~ Type.stringof ~ " lists", { 85 | Type[] testValues; 86 | 87 | before({ 88 | testValues = [cast(Type) 0.350, cast(Type) 0.501, cast(Type) 0.341]; 89 | }); 90 | 91 | it("should approximately compare two lists", { 92 | expect(testValues).to.be.approximately([0.35, 0.50, 0.34], 0.01); 93 | }); 94 | 95 | it("should approximately with a range of 0.00001 compare two lists that are not equal", { 96 | expect(testValues).to.not.be.approximately([0.35, 0.50, 0.34], 0.00001); 97 | }); 98 | 99 | it("should approximately with a range of 0.001 compare two lists that are not equal", { 100 | expect(testValues).to.not.be.approximately([0.35, 0.50, 0.34], 0.0001); 101 | }); 102 | 103 | it("should approximately with a range of 0.001 compare two lists with different lengths", { 104 | expect(testValues).to.not.be.approximately([0.35, 0.50], 0.001); 105 | }); 106 | 107 | it("should show a detailed error message when two lists should be approximately equal but they are not", { 108 | auto msg = ({ 109 | expect(testValues).to.be.approximately([0.35, 0.50, 0.34], 0.0001); 110 | }).should.throwException!TestException.msg; 111 | 112 | msg.should.contain("Expected:[0.35±0.0001, 0.5±0.0001, 0.34±0.0001]"); 113 | msg.should.contain("Missing:[0.501±0.0001, 0.341±0.0001]"); 114 | }); 115 | 116 | it("should show a detailed error message when two lists are approximately equal but they should not", { 117 | auto msg = ({ 118 | expect(testValues).to.not.be.approximately(testValues, 0.0001); 119 | }).should.throwException!TestException.msg; 120 | 121 | msg.should.contain("Expected:not [0.35±0.0001, 0.501±0.0001, 0.341±0.0001]"); 122 | }); 123 | }); 124 | } 125 | }); 126 | -------------------------------------------------------------------------------- /test/operations/arrayContain.d: -------------------------------------------------------------------------------- 1 | module test.operations.arrayContain; 2 | 3 | import fluentasserts.core.expect; 4 | import fluentasserts.core.serializers; 5 | import fluent.asserts; 6 | 7 | import trial.discovery.spec; 8 | 9 | import std.string; 10 | import std.conv; 11 | import std.meta; 12 | import std.algorithm; 13 | import std.range; 14 | 15 | alias s = Spec!({ 16 | alias NumericTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real /*, ifloat, idouble, ireal, cfloat, cdouble, creal*/); 17 | static foreach(Type; NumericTypes) { 18 | describe("using a range of " ~ Type.stringof, { 19 | Type[] testValues; 20 | Type[] someTestValues; 21 | Type[] otherTestValues; 22 | 23 | static if(is(ifloat == Type) || is(idouble == Type) || is(ireal == Type)) { 24 | before({ 25 | testValues = [ 40i, 41i, 42i]; 26 | someTestValues = [ 42i, 41i]; 27 | otherTestValues = [ 50i, 51i, 52i ]; 28 | }); 29 | } else { 30 | before({ 31 | testValues = [ cast(Type) 40, cast(Type) 41, cast(Type) 42 ]; 32 | someTestValues = [ cast(Type) 42, cast(Type) 41 ]; 33 | otherTestValues = [ cast(Type) 50, cast(Type) 51 ]; 34 | }); 35 | } 36 | 37 | it("should find two values in a list", { 38 | expect(testValues.map!"a").to.contain(someTestValues); 39 | }); 40 | 41 | it("should find a value in a list", { 42 | expect(testValues.map!"a").to.contain(someTestValues[0]); 43 | }); 44 | 45 | it("should find other values in a list", { 46 | expect(testValues.map!"a").to.not.contain(otherTestValues); 47 | }); 48 | 49 | it("should find other value in a list", { 50 | expect(testValues.map!"a").to.not.contain(otherTestValues[0]); 51 | }); 52 | 53 | it("should show a detailed error message when the list does not contain 2 values", { 54 | auto msg = ({ 55 | expect(testValues.map!"a").to.contain([4, 5]); 56 | }).should.throwException!TestException.msg; 57 | 58 | msg.split('\n')[0].should.equal(testValues.to!string ~ " should contain [4, 5]. [4, 5] are missing from " ~ testValues.to!string ~ "."); 59 | msg.split('\n')[2].strip.should.equal("Expected:to contain all [4, 5]"); 60 | msg.split('\n')[3].strip.should.equal("Actual:" ~ testValues.to!string); 61 | }); 62 | 63 | it("should show a detailed error message when the list does not contain 2 values", { 64 | auto msg = ({ 65 | expect(testValues.map!"a").to.not.contain(testValues[0..2]); 66 | }).should.throwException!TestException.msg; 67 | 68 | msg.split('\n')[0].should.equal(testValues.to!string ~ " should not contain " ~ testValues[0..2].to!string ~ ". " ~ testValues[0..2].to!string ~ " are present in " ~ testValues.to!string ~ "."); 69 | msg.split('\n')[2].strip.should.equal("Expected:to not contain any " ~ testValues[0..2].to!string); 70 | msg.split('\n')[3].strip.should.equal("Actual:" ~ testValues.to!string); 71 | }); 72 | 73 | it("should show a detailed error message when the list does not contain a value", { 74 | auto msg = ({ 75 | expect(testValues.map!"a").to.contain(otherTestValues[0]); 76 | }).should.throwException!TestException.msg; 77 | 78 | msg.split('\n')[0].should.equal(testValues.to!string ~ " should contain " ~ otherTestValues[0].to!string ~ ". " ~ otherTestValues[0].to!string ~ " is missing from " ~ testValues.to!string ~ "."); 79 | msg.split('\n')[2].strip.should.equal("Expected:to contain " ~ otherTestValues[0].to!string); 80 | msg.split('\n')[3].strip.should.equal("Actual:" ~ testValues.to!string); 81 | }); 82 | 83 | it("should show a detailed error message when the list does contains a value", { 84 | auto msg = ({ 85 | expect(testValues.map!"a").to.not.contain(testValues[0]); 86 | }).should.throwException!TestException.msg; 87 | 88 | msg.split('\n')[0].should.equal(testValues.to!string ~ " should not contain " ~ testValues[0].to!string ~ ". " ~ testValues[0].to!string ~ " is present in " ~ testValues.to!string ~ "."); 89 | msg.split('\n')[2].strip.should.equal("Expected:to not contain " ~ testValues[0].to!string); 90 | msg.split('\n')[3].strip.should.equal("Actual:" ~ testValues.to!string); 91 | }); 92 | }); 93 | } 94 | 95 | describe("using a range of Objects", { 96 | Thing[] testValues; 97 | Thing[] someTestValues; 98 | Thing[] otherTestValues; 99 | 100 | string strTestValues; 101 | string strSomeTestValues; 102 | string strOtherTestValues; 103 | 104 | before({ 105 | testValues = [ new Thing(40), new Thing(41), new Thing(42) ]; 106 | someTestValues = [ new Thing(42), new Thing(41) ]; 107 | otherTestValues = [ new Thing(50), new Thing(51) ]; 108 | 109 | strTestValues = SerializerRegistry.instance.niceValue(testValues); 110 | strSomeTestValues = SerializerRegistry.instance.niceValue(someTestValues); 111 | strOtherTestValues = SerializerRegistry.instance.niceValue(strOtherTestValues); 112 | }); 113 | 114 | it("should find two values in a list", { 115 | expect(testValues.map!"a").to.contain(someTestValues); 116 | }); 117 | 118 | it("should find a value in a list", { 119 | expect(testValues.map!"a").to.contain(someTestValues[0]); 120 | }); 121 | 122 | it("should find other values in a list", { 123 | expect(testValues.map!"a").to.not.contain(otherTestValues); 124 | }); 125 | 126 | it("should find other value in a list", { 127 | expect(testValues.map!"a").to.not.contain(otherTestValues[0]); 128 | }); 129 | 130 | it("should show a detailed error message when the list does not contain 2 values", { 131 | auto msg = ({ 132 | expect(testValues.map!"a").to.contain([4, 5]); 133 | }).should.throwException!TestException.msg; 134 | 135 | msg.split('\n')[2].strip.should.equal("Expected:to contain all [4, 5]"); 136 | msg.split('\n')[3].strip.should.equal("Actual:" ~ strTestValues.to!string); 137 | }); 138 | 139 | it("should show a detailed error message when the list does not contain 2 values", { 140 | auto msg = ({ 141 | expect(testValues.map!"a").to.not.contain(testValues[0..2]); 142 | }).should.throwException!TestException.msg; 143 | 144 | msg.split('\n')[2].strip.should.equal("Expected:to not contain any " ~ SerializerRegistry.instance.niceValue(testValues[0..2])); 145 | msg.split('\n')[3].strip.should.equal("Actual:" ~ strTestValues.to!string); 146 | }); 147 | 148 | it("should show a detailed error message when the list does not contain a value", { 149 | auto msg = ({ 150 | expect(testValues.map!"a").to.contain(otherTestValues[0]); 151 | }).should.throwException!TestException.msg; 152 | 153 | msg.split('\n')[2].strip.should.equal("Expected:to contain " ~ SerializerRegistry.instance.niceValue(otherTestValues[0])); 154 | msg.split('\n')[3].strip.should.equal("Actual:" ~ strTestValues); 155 | }); 156 | 157 | it("should show a detailed error message when the list does contains a value", { 158 | auto msg = ({ 159 | expect(testValues.map!"a").to.not.contain(testValues[0]); 160 | }).should.throwException!TestException.msg; 161 | 162 | msg.split('\n')[2].strip.should.equal("Expected:to not contain " ~ SerializerRegistry.instance.niceValue(testValues[0])); 163 | msg.split('\n')[3].strip.should.equal("Actual:" ~ strTestValues); 164 | }); 165 | }); 166 | }); 167 | 168 | version(unittest) : 169 | class Thing { 170 | int x; 171 | this(int x) { this.x = x; } 172 | override bool opEquals(Object o) { 173 | if(typeid(this) != typeid(o)) return false; 174 | alias a = this; 175 | auto b = cast(typeof(this)) o; 176 | return a.x == b.x; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /test/operations/beNull.d: -------------------------------------------------------------------------------- 1 | module test.operations.beNull; 2 | 3 | import fluentasserts.core.expect; 4 | import fluent.asserts; 5 | 6 | import trial.discovery.spec; 7 | 8 | import std.string; 9 | import std.conv; 10 | import std.meta; 11 | 12 | alias s = Spec!({ 13 | describe("using delegates", { 14 | void delegate() value; 15 | describe("when the delegate is set", { 16 | beforeEach({ 17 | void test() {} 18 | value = &test; 19 | }); 20 | 21 | it("should not throw when it is expected not to be null", { 22 | expect(value).not.to.beNull; 23 | }); 24 | 25 | it("should throw when it is expected to be null", { 26 | auto msg = expect({ 27 | expect(value).to.beNull; 28 | }).to.throwException!TestException.msg; 29 | 30 | msg.split("\n")[0].should.equal(" should be null."); 31 | msg.split("\n")[2].strip.should.equal("Expected:null"); 32 | msg.split("\n")[3].strip.should.equal("Actual:callable"); 33 | }); 34 | }); 35 | 36 | describe("when the delegate is not set", { 37 | beforeEach({ 38 | value = null; 39 | }); 40 | 41 | it("should not throw when it is expected to be null", { 42 | expect(value).to.beNull; 43 | }); 44 | 45 | it("should throw when it is expected not to be null", { 46 | auto msg = expect({ 47 | expect(value).not.to.beNull; 48 | }).to.throwException!TestException.msg; 49 | 50 | msg.split("\n")[0].should.equal(" should not be null."); 51 | msg.split("\n")[2].strip.should.equal("Expected:not null"); 52 | msg.split("\n")[3].strip.should.equal("Actual:null"); 53 | }); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/operations/between.d: -------------------------------------------------------------------------------- 1 | module test.operations.between; 2 | 3 | import fluentasserts.core.expect; 4 | import fluent.asserts; 5 | 6 | import trial.discovery.spec; 7 | 8 | import std.string; 9 | import std.conv; 10 | import std.meta; 11 | import std.datetime; 12 | 13 | alias s = Spec!({ 14 | alias NumericTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real); 15 | 16 | static foreach(Type; NumericTypes) { 17 | describe("using " ~ Type.stringof ~ " values", { 18 | Type smallValue; 19 | Type largeValue; 20 | Type middleValue; 21 | 22 | before({ 23 | smallValue = cast(Type) 40; 24 | largeValue = cast(Type) 50; 25 | middleValue = cast(Type) 45; 26 | }); 27 | 28 | it("should be able to check if a value is inside an interval", { 29 | expect(middleValue).to.be.between(smallValue, largeValue); 30 | expect(middleValue).to.be.between(largeValue, smallValue); 31 | expect(middleValue).to.be.within(smallValue, largeValue); 32 | }); 33 | 34 | it("should be able to check if a value is outside an interval", { 35 | expect(largeValue).to.not.be.between(smallValue, largeValue); 36 | expect(largeValue).to.not.be.between(largeValue, smallValue); 37 | expect(largeValue).to.not.be.within(smallValue, largeValue); 38 | }); 39 | 40 | it("should throw a detailed error when the value equal to the max value of the interval", { 41 | auto msg = ({ 42 | expect(largeValue).to.be.between(smallValue, largeValue); 43 | }).should.throwException!TestException.msg; 44 | 45 | msg.split("\n")[0].should.equal(largeValue.to!string ~ " should be between " ~ smallValue.to!string ~ " and " ~ largeValue.to!string ~ ". " ~ largeValue.to!string ~ " is greater than or equal to " ~ largeValue.to!string ~ "."); 46 | msg.split("\n")[2].strip.should.equal("Expected:a value inside (" ~ smallValue.to!string ~ ", " ~ largeValue.to!string ~ ") interval"); 47 | msg.split("\n")[3].strip.should.equal("Actual:" ~ largeValue.to!string); 48 | }); 49 | 50 | it("should throw a detailed error when the value equal to the min value of the interval", { 51 | auto msg = ({ 52 | expect(smallValue).to.be.between(smallValue, largeValue); 53 | }).should.throwException!TestException.msg; 54 | 55 | msg.split("\n")[0].should.equal(smallValue.to!string ~ " should be between " ~ smallValue.to!string ~ " and " ~ largeValue.to!string ~ ". " ~ smallValue.to!string ~ " is less than or equal to " ~ smallValue.to!string ~ "."); 56 | msg.split("\n")[2].strip.should.equal("Expected:a value inside (" ~ smallValue.to!string ~ ", " ~ largeValue.to!string ~ ") interval"); 57 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.to!string); 58 | }); 59 | 60 | it("should throw a detailed error when the negated assert fails", { 61 | auto msg = ({ 62 | expect(middleValue).to.not.be.between(smallValue, largeValue); 63 | }).should.throwException!TestException.msg; 64 | 65 | msg.split("\n")[0].should.startWith(middleValue.to!string ~ " should not be between " ~ smallValue.to!string ~ " and " ~ largeValue.to!string ~ "."); 66 | msg.split("\n")[2].strip.should.equal("Expected:a value outside (" ~ smallValue.to!string ~ ", " ~ largeValue.to!string ~ ") interval"); 67 | msg.split("\n")[3].strip.should.equal("Actual:" ~ middleValue.to!string); 68 | }); 69 | }); 70 | } 71 | 72 | describe("using Duration values", { 73 | Duration smallValue; 74 | Duration largeValue; 75 | Duration middleValue; 76 | 77 | before({ 78 | smallValue = 40.seconds; 79 | largeValue = 50.seconds; 80 | middleValue = 45.seconds; 81 | }); 82 | 83 | it("should be able to check if a value is inside an interval", { 84 | expect(middleValue).to.be.between(smallValue, largeValue); 85 | expect(middleValue).to.be.between(largeValue, smallValue); 86 | expect(middleValue).to.be.within(smallValue, largeValue); 87 | }); 88 | 89 | it("should be able to check if a value is outside an interval", { 90 | expect(largeValue).to.not.be.between(smallValue, largeValue); 91 | expect(largeValue).to.not.be.between(largeValue, smallValue); 92 | expect(largeValue).to.not.be.within(smallValue, largeValue); 93 | }); 94 | 95 | it("should throw a detailed error when the value equal to the max value of the interval", { 96 | auto msg = ({ 97 | expect(largeValue).to.be.between(smallValue, largeValue); 98 | }).should.throwException!TestException.msg; 99 | 100 | msg.split("\n")[0].should.equal(largeValue.to!string ~ " should be between " ~ smallValue.to!string ~ " and " ~ largeValue.to!string ~ ". " ~ largeValue.to!string ~ " is greater than or equal to " ~ largeValue.to!string ~ "."); 101 | msg.split("\n")[2].strip.should.equal("Expected:a value inside (" ~ smallValue.to!string ~ ", " ~ largeValue.to!string ~ ") interval"); 102 | msg.split("\n")[3].strip.should.equal("Actual:" ~ largeValue.to!string); 103 | }); 104 | 105 | it("should throw a detailed error when the value equal to the min value of the interval", { 106 | auto msg = ({ 107 | expect(smallValue).to.be.between(smallValue, largeValue); 108 | }).should.throwException!TestException.msg; 109 | 110 | msg.split("\n")[0].should.equal(smallValue.to!string ~ " should be between " ~ smallValue.to!string ~ " and " ~ largeValue.to!string ~ ". " ~ smallValue.to!string ~ " is less than or equal to " ~ smallValue.to!string ~ "."); 111 | msg.split("\n")[2].strip.should.equal("Expected:a value inside (" ~ smallValue.to!string ~ ", " ~ largeValue.to!string ~ ") interval"); 112 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.to!string); 113 | }); 114 | 115 | it("should throw a detailed error when the negated assert fails", { 116 | auto msg = ({ 117 | expect(middleValue).to.not.be.between(smallValue, largeValue); 118 | }).should.throwException!TestException.msg; 119 | 120 | msg.split("\n")[0].should.startWith(middleValue.to!string ~ " should not be between " ~ smallValue.to!string ~ " and " ~ largeValue.to!string ~ "."); 121 | msg.split("\n")[2].strip.should.equal("Expected:a value outside (" ~ smallValue.to!string ~ ", " ~ largeValue.to!string ~ ") interval"); 122 | msg.split("\n")[3].strip.should.equal("Actual:" ~ middleValue.to!string); 123 | }); 124 | }); 125 | 126 | describe("using SysTime values", { 127 | SysTime smallValue; 128 | SysTime largeValue; 129 | SysTime middleValue; 130 | 131 | before({ 132 | smallValue = Clock.currTime; 133 | largeValue = Clock.currTime + 40.seconds; 134 | middleValue = Clock.currTime + 35.seconds; 135 | }); 136 | 137 | it("should be able to check if a value is inside an interval", { 138 | expect(middleValue).to.be.between(smallValue, largeValue); 139 | expect(middleValue).to.be.between(largeValue, smallValue); 140 | expect(middleValue).to.be.within(smallValue, largeValue); 141 | }); 142 | 143 | it("should be able to check if a value is outside an interval", { 144 | expect(largeValue).to.not.be.between(smallValue, largeValue); 145 | expect(largeValue).to.not.be.between(largeValue, smallValue); 146 | expect(largeValue).to.not.be.within(smallValue, largeValue); 147 | }); 148 | 149 | it("should throw a detailed error when the value equal to the max value of the interval", { 150 | auto msg = ({ 151 | expect(largeValue).to.be.between(smallValue, largeValue); 152 | }).should.throwException!TestException.msg; 153 | 154 | msg.split("\n")[0].should.equal(largeValue.toISOExtString ~ " should be between " ~ smallValue.toISOExtString ~ " and " ~ largeValue.toISOExtString ~ ". " ~ largeValue.toISOExtString ~ " is greater than or equal to " ~ largeValue.to!string ~ "."); 155 | }); 156 | 157 | it("should throw a detailed error when the value equal to the min value of the interval", { 158 | auto msg = ({ 159 | expect(smallValue).to.be.between(smallValue, largeValue); 160 | }).should.throwException!TestException.msg; 161 | 162 | msg.split("\n")[0].should.equal(smallValue.toISOExtString ~ " should be between " ~ smallValue.toISOExtString ~ " and " ~ largeValue.toISOExtString ~ ". " ~ smallValue.toISOExtString ~ " is less than or equal to " ~ smallValue.to!string ~ "."); 163 | }); 164 | 165 | it("should throw a detailed error when the negated assert fails", { 166 | auto msg = ({ 167 | expect(middleValue).to.not.be.between(smallValue, largeValue); 168 | }).should.throwException!TestException.msg; 169 | 170 | msg.split("\n")[0].should.startWith(middleValue.toISOExtString ~ " should not be between " ~ smallValue.toISOExtString ~ " and " ~ largeValue.toISOExtString ~ "."); 171 | }); 172 | }); 173 | }); 174 | -------------------------------------------------------------------------------- /test/operations/contain.d: -------------------------------------------------------------------------------- 1 | module test.operations.contain; 2 | 3 | import fluentasserts.core.expect; 4 | import fluent.asserts; 5 | 6 | import trial.discovery.spec; 7 | 8 | import std.string; 9 | import std.conv; 10 | import std.meta; 11 | import std.algorithm; 12 | import std.range; 13 | 14 | alias s = Spec!({ 15 | alias StringTypes = AliasSeq!(string, wstring, dstring); 16 | 17 | static foreach(Type; StringTypes) { 18 | describe("using " ~ Type.stringof ~ " values", { 19 | Type testValue; 20 | 21 | Type[] listOfOtherValues; 22 | Type[] listOfValues; 23 | 24 | before({ 25 | testValue = "test string".to!Type; 26 | listOfOtherValues = ["string".to!Type, "test".to!Type]; 27 | listOfOtherValues = ["other".to!Type, "message".to!Type]; 28 | }); 29 | 30 | it("should find two substrings", { 31 | expect(testValue).to.contain(["string", "test"]); 32 | }); 33 | 34 | it("should not find matches from a list of strings", { 35 | expect(testValue).to.not.contain(["other", "message"]); 36 | }); 37 | 38 | it("should find a char", { 39 | expect(testValue).to.contain('s'); 40 | }); 41 | 42 | it("should not find a char that is not in the string", { 43 | expect(testValue).to.not.contain('z'); 44 | }); 45 | 46 | it("should show a detailed error message when the strings are not found", { 47 | auto msg = ({ 48 | expect(testValue).to.contain(["other", "message"]); 49 | }).should.throwException!TestException.msg; 50 | 51 | msg.split("\n")[0].should.equal(`"test string" should contain ["other", "message"]. ["other", "message"] are missing from "test string".`); 52 | msg.split("\n")[2].strip.should.equal("Expected:to contain all [\"other\", \"message\"]"); 53 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 54 | }); 55 | 56 | it("should throw an error when the string contains substrings that it should not", { 57 | auto msg = ({ 58 | expect(testValue).to.not.contain(["test", "string"]); 59 | }).should.throwException!TestException.msg; 60 | 61 | msg.split("\n")[0].should.equal(`"test string" should not contain ["test", "string"]. ["test", "string"] are present in "test string".`); 62 | msg.split("\n")[2].strip.should.equal("Expected:to not contain any [\"test\", \"string\"]"); 63 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 64 | }); 65 | 66 | it("should throw an error when the string does not contains a substring", { 67 | auto msg = ({ 68 | expect(testValue).to.contain("other"); 69 | }).should.throwException!TestException.msg; 70 | 71 | msg.split("\n")[0].should.equal(`"test string" should contain "other". other is missing from "test string".`); 72 | msg.split("\n")[2].strip.should.equal("Expected:to contain \"other\""); 73 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 74 | }); 75 | 76 | it("should throw an error when the string contains a substring that it should not", { 77 | auto msg = ({ 78 | expect(testValue).to.not.contain("test"); 79 | }).should.throwException!TestException.msg; 80 | 81 | msg.split("\n")[0].should.equal(`"test string" should not contain "test". test is present in "test string".`); 82 | msg.split("\n")[2].strip.should.equal("Expected:to not contain \"test\""); 83 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 84 | }); 85 | 86 | it("should throw an error when the string does not contains a char", { 87 | auto msg = ({ 88 | expect(testValue).to.contain('o'); 89 | }).should.throwException!TestException.msg; 90 | 91 | msg.split("\n")[0].should.equal(`"test string" should contain 'o'. o is missing from "test string".`); 92 | msg.split("\n")[2].strip.should.equal("Expected:to contain 'o'"); 93 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 94 | }); 95 | 96 | it("should throw an error when the string contains a char that it should not", { 97 | auto msg = ({ 98 | expect(testValue).to.not.contain('t'); 99 | }).should.throwException!TestException.msg; 100 | 101 | msg.split("\n")[0].should.equal(`"test string" should not contain 't'. t is present in "test string".`); 102 | msg.split("\n")[2].strip.should.equal("Expected:to not contain 't'"); 103 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 104 | }); 105 | }); 106 | 107 | describe("using a range of " ~ Type.stringof ~ " values", { 108 | Type testValue; 109 | 110 | Type[] listOfOtherValues; 111 | Type[] listOfValues; 112 | 113 | before({ 114 | testValue = "test string".to!Type; 115 | }); 116 | 117 | it("should find two substrings", { 118 | expect(testValue).to.contain(["string", "test"].inputRangeObject); 119 | }); 120 | 121 | it("should not find matches from a list of strings", { 122 | expect(testValue).to.not.contain(["other", "message"].inputRangeObject); 123 | }); 124 | 125 | it("should show a detailed error message when the strings are not found", { 126 | auto msg = ({ 127 | expect(testValue).to.contain(["other", "message"].inputRangeObject); 128 | }).should.throwException!TestException.msg; 129 | 130 | msg.split("\n")[0].should.equal(`"test string" should contain ["other", "message"]. ["other", "message"] are missing from "test string".`); 131 | msg.split("\n")[2].strip.should.equal("Expected:to contain all [\"other\", \"message\"]"); 132 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 133 | }); 134 | 135 | it("should throw an error when the string contains substrings that it should not", { 136 | auto msg = ({ 137 | expect(testValue).to.not.contain(["test", "string"].inputRangeObject); 138 | }).should.throwException!TestException.msg; 139 | 140 | msg.split("\n")[0].should.equal(`"test string" should not contain ["test", "string"]. ["test", "string"] are present in "test string".`); 141 | msg.split("\n")[2].strip.should.equal("Expected:to not contain any [\"test\", \"string\"]"); 142 | msg.split("\n")[3].strip.should.equal("Actual:test string"); 143 | }); 144 | }); 145 | } 146 | }); 147 | -------------------------------------------------------------------------------- /test/operations/endWith.d: -------------------------------------------------------------------------------- 1 | module test.operations.endWith; 2 | 3 | import fluentasserts.core.expect; 4 | import fluent.asserts; 5 | 6 | import trial.discovery.spec; 7 | 8 | import std.string; 9 | import std.conv; 10 | import std.meta; 11 | 12 | alias s = Spec!({ 13 | describe("special cases", { 14 | it("should check that a multi line string ends with a certain substring", { 15 | expect("str\ning").to.endWith("ing"); 16 | }); 17 | }); 18 | 19 | alias StringTypes = AliasSeq!(string, wstring, dstring); 20 | 21 | static foreach(Type; StringTypes) { 22 | describe("using " ~ Type.stringof ~ " values", { 23 | Type testValue; 24 | 25 | before({ 26 | testValue = "test string".to!Type; 27 | }); 28 | 29 | it("should check that a string ends with a certain substring", { 30 | expect(testValue).to.endWith("string"); 31 | }); 32 | 33 | it("should check that a string ends with a certain char", { 34 | expect(testValue).to.endWith('g'); 35 | }); 36 | 37 | it("should check that a string does not end with a certain substring", { 38 | expect(testValue).to.not.endWith("other"); 39 | }); 40 | 41 | it("should check that a string does not end with a certain char", { 42 | expect(testValue).to.not.endWith('o'); 43 | }); 44 | 45 | it("should throw a detailed error when the string does not end with the substring what was expected", { 46 | auto msg = ({ 47 | expect(testValue).to.endWith("other"); 48 | }).should.throwException!TestException.msg; 49 | 50 | msg.split("\n")[0].should.contain(`"test string" should end with "other". "test string" does not end with "other".`); 51 | msg.split("\n")[2].strip.should.equal(`Expected:to end with "other"`); 52 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 53 | }); 54 | 55 | it("should throw a detailed error when the string does not end with the char what was expected", { 56 | auto msg = ({ 57 | expect(testValue).to.endWith('o'); 58 | }).should.throwException!TestException.msg; 59 | 60 | msg.split("\n")[0].should.contain(`"test string" should end with 'o'. "test string" does not end with 'o'.`); 61 | msg.split("\n")[2].strip.should.equal(`Expected:to end with 'o'`); 62 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 63 | }); 64 | 65 | it("should throw a detailed error when the string does end with the unexpected substring", { 66 | auto msg = ({ 67 | expect(testValue).to.not.endWith("string"); 68 | }).should.throwException!TestException.msg; 69 | 70 | msg.split("\n")[0].should.contain(`"test string" should not end with "string". "test string" ends with "string".`); 71 | msg.split("\n")[2].strip.should.equal(`Expected:to not end with "string"`); 72 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 73 | }); 74 | 75 | it("should throw a detailed error when the string does end with the unexpected char", { 76 | auto msg = ({ 77 | expect(testValue).to.not.endWith('g'); 78 | }).should.throwException!TestException.msg; 79 | 80 | msg.split("\n")[0].should.contain(`"test string" should not end with 'g'. "test string" ends with 'g'.`); 81 | msg.split("\n")[2].strip.should.equal(`Expected:to not end with 'g'`); 82 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 83 | }); 84 | }); 85 | } 86 | }); 87 | -------------------------------------------------------------------------------- /test/operations/greaterOrEqualTo.d: -------------------------------------------------------------------------------- 1 | module test.operations.greaterOrEqualTo; 2 | 3 | import fluentasserts.core.expect; 4 | import fluent.asserts; 5 | 6 | import trial.discovery.spec; 7 | 8 | import std.string; 9 | import std.conv; 10 | import std.meta; 11 | import std.datetime; 12 | 13 | alias s = Spec!({ 14 | alias NumericTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real); 15 | 16 | static foreach(Type; NumericTypes) { 17 | describe("using " ~ Type.stringof ~ " values", { 18 | Type smallValue; 19 | Type largeValue; 20 | 21 | before({ 22 | smallValue = cast(Type) 40; 23 | largeValue = cast(Type) 50; 24 | }); 25 | 26 | it("should be able to compare two values", { 27 | expect(largeValue).to.be.greaterOrEqualTo(smallValue); 28 | expect(largeValue).to.be.greaterOrEqualTo(largeValue); 29 | }); 30 | 31 | it("should be able to compare two values using negation", { 32 | expect(smallValue).not.to.be.greaterOrEqualTo(largeValue); 33 | }); 34 | 35 | it("should throw a detailed error when the comparison fails", { 36 | auto msg = ({ 37 | expect(smallValue).to.be.greaterOrEqualTo(largeValue); 38 | }).should.throwException!TestException.msg; 39 | 40 | msg.split("\n")[0].should.equal(smallValue.to!string ~ " should be greater or equal to " ~ largeValue.to!string ~ ". " ~ smallValue.to!string ~ " is less than " ~ largeValue.to!string ~ "."); 41 | msg.split("\n")[2].strip.should.equal("Expected:greater or equal than " ~ largeValue.to!string); 42 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.to!string); 43 | }); 44 | 45 | it("should throw a detailed error when the negated coparison fails", { 46 | auto msg = ({ 47 | expect(largeValue).not.to.be.greaterOrEqualTo(smallValue); 48 | }).should.throwException!TestException.msg; 49 | 50 | msg.split("\n")[0].should.equal(largeValue.to!string ~ " should not be greater or equal to " ~ smallValue.to!string ~ ". " ~ largeValue.to!string ~ " is greater or equal than " ~ smallValue.to!string ~ "."); 51 | msg.split("\n")[2].strip.should.equal("Expected:less than " ~ smallValue.to!string); 52 | msg.split("\n")[3].strip.should.equal("Actual:" ~ largeValue.to!string); 53 | }); 54 | }); 55 | } 56 | 57 | describe("using Duration values", { 58 | Duration smallValue; 59 | Duration largeValue; 60 | 61 | before({ 62 | smallValue = 40.seconds; 63 | largeValue = 41.seconds; 64 | }); 65 | 66 | it("should be able to compare two values", { 67 | expect(largeValue).to.be.greaterOrEqualTo(smallValue); 68 | }); 69 | 70 | it("should be able to compare two values using negation", { 71 | expect(smallValue).not.to.be.greaterOrEqualTo(largeValue); 72 | }); 73 | 74 | it("should not throw a detailed error when the number is compared with itself", { 75 | expect(smallValue).to.be.greaterOrEqualTo(smallValue); 76 | }); 77 | 78 | it("should throw a detailed error when the negated comparison fails", { 79 | auto msg = ({ 80 | expect(largeValue).not.to.be.greaterOrEqualTo(smallValue); 81 | }).should.throwException!TestException.msg; 82 | 83 | msg.split("\n")[0].should.equal(largeValue.to!string ~ " should not be greater or equal to " ~ smallValue.to!string ~ ". " ~ 84 | largeValue.to!string ~ " is greater or equal than " ~ smallValue.to!string ~ "."); 85 | msg.split("\n")[2].strip.should.equal("Expected:less than " ~ smallValue.to!string); 86 | msg.split("\n")[3].strip.should.equal("Actual:" ~ largeValue.to!string); 87 | }); 88 | }); 89 | 90 | describe("using SysTime values", { 91 | SysTime smallValue; 92 | SysTime largeValue; 93 | 94 | before({ 95 | smallValue = Clock.currTime; 96 | largeValue = smallValue + 4.seconds; 97 | }); 98 | 99 | it("should be able to compare two values", { 100 | expect(largeValue).to.be.greaterOrEqualTo(smallValue); 101 | expect(largeValue).to.be.above(smallValue); 102 | }); 103 | 104 | it("should be able to compare two values using negation", { 105 | expect(smallValue).not.to.be.greaterOrEqualTo(largeValue); 106 | expect(smallValue).not.to.be.above(largeValue); 107 | }); 108 | 109 | it("should not throw a detailed error when the number is compared with itself", { 110 | expect(smallValue).to.be.greaterOrEqualTo(smallValue); 111 | }); 112 | 113 | it("should throw a detailed error when the negated comparison fails", { 114 | auto msg = ({ 115 | expect(largeValue).not.to.be.greaterOrEqualTo(smallValue); 116 | }).should.throwException!TestException.msg; 117 | 118 | msg.split("\n")[0].should.equal(largeValue.toISOExtString ~ " should not be greater or equal to " ~ smallValue.toISOExtString ~ ". " ~ 119 | largeValue.toISOExtString ~ " is greater or equal than " ~ smallValue.toISOExtString ~ "."); 120 | msg.split("\n")[2].strip.should.equal("Expected:less than " ~ smallValue.toISOExtString); 121 | msg.split("\n")[3].strip.should.equal("Actual:" ~ largeValue.toISOExtString); 122 | }); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /test/operations/greaterThan.d: -------------------------------------------------------------------------------- 1 | module test.operations.greaterThan; 2 | 3 | import fluentasserts.core.expect; 4 | import fluent.asserts; 5 | 6 | import trial.discovery.spec; 7 | 8 | import std.string; 9 | import std.conv; 10 | import std.meta; 11 | import std.datetime; 12 | 13 | alias s = Spec!({ 14 | alias NumericTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real); 15 | 16 | static foreach(Type; NumericTypes) { 17 | describe("using " ~ Type.stringof ~ " values", { 18 | Type smallValue; 19 | Type largeValue; 20 | 21 | before({ 22 | smallValue = cast(Type) 40; 23 | largeValue = cast(Type) 50; 24 | }); 25 | 26 | it("should be able to compare two values", { 27 | expect(largeValue).to.be.greaterThan(smallValue); 28 | expect(largeValue).to.be.above(smallValue); 29 | }); 30 | 31 | it("should be able to compare two values using negation", { 32 | expect(smallValue).not.to.be.greaterThan(largeValue); 33 | expect(smallValue).not.to.be.above(largeValue); 34 | }); 35 | 36 | it("should throw a detailed error when the number is compared with itself", { 37 | auto msg = ({ 38 | expect(smallValue).to.be.greaterThan(smallValue); 39 | }).should.throwException!TestException.msg; 40 | 41 | msg.split("\n")[0].should.equal(smallValue.to!string ~ " should be greater than " ~ smallValue.to!string ~ ". " ~ smallValue.to!string ~ " is less than or equal to " ~ smallValue.to!string ~ "."); 42 | msg.split("\n")[2].strip.should.equal("Expected:greater than " ~ smallValue.to!string); 43 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.to!string); 44 | }); 45 | 46 | it("should throw a detailed error when the comparison fails", { 47 | auto msg = ({ 48 | expect(smallValue).to.be.greaterThan(largeValue); 49 | }).should.throwException!TestException.msg; 50 | 51 | msg.split("\n")[0].should.equal(smallValue.to!string ~ " should be greater than " ~ largeValue.to!string ~ ". " ~ smallValue.to!string ~ " is less than or equal to " ~ largeValue.to!string ~ "."); 52 | msg.split("\n")[2].strip.should.equal("Expected:greater than " ~ largeValue.to!string); 53 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.to!string); 54 | }); 55 | 56 | it("should throw a detailed error when the negated coparison fails", { 57 | auto msg = ({ 58 | expect(largeValue).not.to.be.greaterThan(smallValue); 59 | }).should.throwException!TestException.msg; 60 | 61 | msg.split("\n")[0].should.equal(largeValue.to!string ~ " should not be greater than " ~ smallValue.to!string ~ ". " ~ largeValue.to!string ~ " is greater than " ~ smallValue.to!string ~ "."); 62 | msg.split("\n")[2].strip.should.equal("Expected:less than or equal to " ~ smallValue.to!string); 63 | msg.split("\n")[3].strip.should.equal("Actual:" ~ largeValue.to!string); 64 | }); 65 | }); 66 | } 67 | 68 | describe("using Duration values", { 69 | Duration smallValue; 70 | Duration largeValue; 71 | 72 | before({ 73 | smallValue = 40.seconds; 74 | largeValue = 41.seconds; 75 | }); 76 | 77 | it("should be able to compare two values", { 78 | expect(largeValue).to.be.greaterThan(smallValue); 79 | expect(largeValue).to.be.above(smallValue); 80 | }); 81 | 82 | it("should be able to compare two values using negation", { 83 | expect(smallValue).not.to.be.greaterThan(largeValue); 84 | expect(smallValue).not.to.be.above(largeValue); 85 | }); 86 | 87 | it("should throw a detailed error when the number is compared with itself", { 88 | auto msg = ({ 89 | expect(smallValue).to.be.greaterThan(smallValue); 90 | }).should.throwException!TestException.msg; 91 | 92 | msg.split("\n")[0].should.equal(smallValue.to!string ~ " should be greater than " ~ smallValue.to!string ~ ". " ~ smallValue.to!string ~ " is less than or equal to " ~ smallValue.to!string ~ "."); 93 | msg.split("\n")[2].strip.should.equal("Expected:greater than " ~ smallValue.to!string); 94 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.to!string); 95 | }); 96 | 97 | it("should throw a detailed error when the negated comparison fails", { 98 | auto msg = ({ 99 | expect(largeValue).not.to.be.greaterThan(smallValue); 100 | }).should.throwException!TestException.msg; 101 | 102 | msg.split("\n")[0].should.equal(largeValue.to!string ~ " should not be greater than " ~ smallValue.to!string ~ ". " ~ largeValue.to!string ~ " is greater than " ~ smallValue.to!string ~ "."); 103 | msg.split("\n")[2].strip.should.equal("Expected:less than or equal to " ~ smallValue.to!string); 104 | msg.split("\n")[3].strip.should.equal("Actual:" ~ largeValue.to!string); 105 | }); 106 | }); 107 | 108 | describe("using SysTime values", { 109 | SysTime smallValue; 110 | SysTime largeValue; 111 | 112 | before({ 113 | smallValue = Clock.currTime; 114 | largeValue = smallValue + 4.seconds; 115 | }); 116 | 117 | it("should be able to compare two values", { 118 | expect(largeValue).to.be.greaterThan(smallValue); 119 | expect(largeValue).to.be.above(smallValue); 120 | }); 121 | 122 | it("should be able to compare two values using negation", { 123 | expect(smallValue).not.to.be.greaterThan(largeValue); 124 | expect(smallValue).not.to.be.above(largeValue); 125 | }); 126 | 127 | it("should throw a detailed error when the number is compared with itself", { 128 | auto msg = ({ 129 | expect(smallValue).to.be.greaterThan(smallValue); 130 | }).should.throwException!TestException.msg; 131 | 132 | msg.split("\n")[0].should.equal(smallValue.toISOExtString ~ " should be greater than " ~ smallValue.toISOExtString ~ ". " ~ smallValue.toISOExtString ~ " is less than or equal to " ~ smallValue.toISOExtString ~ "."); 133 | msg.split("\n")[2].strip.should.equal("Expected:greater than " ~ smallValue.toISOExtString); 134 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.toISOExtString); 135 | }); 136 | 137 | it("should throw a detailed error when the negated comparison fails", { 138 | auto msg = ({ 139 | expect(largeValue).not.to.be.greaterThan(smallValue); 140 | }).should.throwException!TestException.msg; 141 | 142 | msg.split("\n")[0].should.equal(largeValue.toISOExtString ~ " should not be greater than " ~ smallValue.toISOExtString ~ ". " ~ largeValue.toISOExtString ~ " is greater than " ~ smallValue.toISOExtString ~ "."); 143 | msg.split("\n")[2].strip.should.equal("Expected:less than or equal to " ~ smallValue.toISOExtString); 144 | msg.split("\n")[3].strip.should.equal("Actual:" ~ largeValue.toISOExtString); 145 | }); 146 | }); 147 | }); 148 | -------------------------------------------------------------------------------- /test/operations/instanceOf.d: -------------------------------------------------------------------------------- 1 | module test.operations.instanceOf; 2 | 3 | import fluentasserts.core.expect; 4 | import fluent.asserts; 5 | 6 | import trial.discovery.spec; 7 | 8 | import std.string; 9 | import std.conv; 10 | import std.meta; 11 | 12 | alias s = Spec!({ 13 | alias NumericTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real); 14 | 15 | 16 | it("should not throw when comparing an object", { 17 | auto value = new Object(); 18 | 19 | expect(value).to.be.instanceOf!Object; 20 | expect(value).to.not.be.instanceOf!string; 21 | }); 22 | 23 | it("should not throw when comparing an Exception with an Object", { 24 | auto value = new Exception("some test"); 25 | 26 | expect(value).to.be.instanceOf!Exception; 27 | expect(value).to.be.instanceOf!Object; 28 | expect(value).to.not.be.instanceOf!string; 29 | }); 30 | 31 | static foreach(Type; NumericTypes) { 32 | describe("using " ~ Type.stringof ~ " values", { 33 | Type value; 34 | 35 | before({ 36 | value = cast(Type) 40; 37 | }); 38 | 39 | it("should be able to compare two types", { 40 | expect(value).to.be.instanceOf!Type; 41 | expect(value).to.not.be.instanceOf!string; 42 | }); 43 | 44 | it("should throw a detailed error when the types do not match", { 45 | auto msg = ({ 46 | expect(value).to.be.instanceOf!string; 47 | }).should.throwException!TestException.msg; 48 | 49 | msg.split("\n")[0].should.equal(value.to!string ~ ` should be instance of "string". ` ~ value.to!string ~ " is instance of " ~ Type.stringof ~ "."); 50 | msg.split("\n")[2].strip.should.equal("Expected:typeof string"); 51 | msg.split("\n")[3].strip.should.equal("Actual:typeof " ~ Type.stringof); 52 | }); 53 | 54 | it("should throw a detailed error when the types match and they should not", { 55 | auto msg = ({ 56 | expect(value).to.not.be.instanceOf!Type; 57 | }).should.throwException!TestException.msg; 58 | 59 | msg.split("\n")[0].should.equal(value.to!string ~ ` should not be instance of "` ~ Type.stringof ~ `". ` ~ value.to!string ~ " is instance of " ~ Type.stringof ~ "."); 60 | msg.split("\n")[2].strip.should.equal("Expected:not typeof " ~ Type.stringof); 61 | msg.split("\n")[3].strip.should.equal("Actual:typeof " ~ Type.stringof); 62 | }); 63 | }); 64 | } 65 | }); 66 | -------------------------------------------------------------------------------- /test/operations/lessOrEqualTo.d: -------------------------------------------------------------------------------- 1 | module test.operations.lessOrEqualTo; 2 | 3 | import fluentasserts.core.expect; 4 | import fluent.asserts; 5 | 6 | import trial.discovery.spec; 7 | 8 | import std.string; 9 | import std.conv; 10 | import std.meta; 11 | import std.datetime; 12 | 13 | alias s = Spec!({ 14 | alias NumericTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real); 15 | 16 | static foreach(Type; NumericTypes) { 17 | describe("using " ~ Type.stringof ~ " values", { 18 | Type smallValue; 19 | Type largeValue; 20 | 21 | before({ 22 | smallValue = cast(Type) 40; 23 | largeValue = cast(Type) 50; 24 | }); 25 | 26 | it("should be able to compare two values", { 27 | expect(smallValue).to.be.lessOrEqualTo(largeValue); 28 | expect(smallValue).to.be.lessOrEqualTo(smallValue); 29 | }); 30 | 31 | it("should be able to compare two values using negation", { 32 | expect(largeValue).not.to.be.lessOrEqualTo(smallValue); 33 | }); 34 | 35 | it("should throw a detailed error when the comparison fails", { 36 | auto msg = ({ 37 | expect(largeValue).to.be.lessOrEqualTo(smallValue); 38 | }).should.throwException!TestException.msg; 39 | 40 | msg.split("\n")[0].should.equal(largeValue.to!string ~ " should be less or equal to " ~ smallValue.to!string ~ ". " ~ largeValue.to!string ~ " is greater than " ~ smallValue.to!string ~ "."); 41 | msg.split("\n")[2].strip.should.equal("Expected:less or equal to " ~ smallValue.to!string); 42 | msg.split("\n")[3].strip.should.equal("Actual:" ~ largeValue.to!string); 43 | }); 44 | 45 | it("should throw a detailed error when the negated comparison fails", { 46 | auto msg = ({ 47 | expect(smallValue).not.to.be.lessOrEqualTo(largeValue); 48 | }).should.throwException!TestException.msg; 49 | 50 | msg.split("\n")[0].should.equal(smallValue.to!string ~ " should not be less or equal to " ~ largeValue.to!string ~ ". " ~ smallValue.to!string ~ " is less or equal to " ~ largeValue.to!string ~ "."); 51 | msg.split("\n")[2].strip.should.equal("Expected:greater than " ~ largeValue.to!string); 52 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.to!string); 53 | }); 54 | }); 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /test/operations/lessThan.d: -------------------------------------------------------------------------------- 1 | module test.operations.lessThan; 2 | 3 | import fluentasserts.core.expect; 4 | import fluent.asserts; 5 | 6 | import trial.discovery.spec; 7 | 8 | import std.string; 9 | import std.conv; 10 | import std.meta; 11 | import std.datetime; 12 | 13 | alias s = Spec!({ 14 | alias NumericTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real); 15 | 16 | static foreach(Type; NumericTypes) { 17 | describe("using " ~ Type.stringof ~ " values", { 18 | Type smallValue; 19 | Type largeValue; 20 | 21 | before({ 22 | smallValue = cast(Type) 40; 23 | largeValue = cast(Type) 50; 24 | }); 25 | 26 | it("should be able to compare two values", { 27 | expect(smallValue).to.be.lessThan(largeValue); 28 | expect(smallValue).to.be.below(largeValue); 29 | }); 30 | 31 | it("should be able to compare two values using negation", { 32 | expect(largeValue).not.to.be.lessThan(smallValue); 33 | expect(largeValue).not.to.be.below(smallValue); 34 | }); 35 | 36 | it("should throw a detailed error when the number is compared with itself", { 37 | auto msg = ({ 38 | expect(smallValue).to.be.lessThan(smallValue); 39 | }).should.throwException!TestException.msg; 40 | 41 | msg.split("\n")[0].should.equal(smallValue.to!string ~ " should be less than " ~ smallValue.to!string ~ ". " ~ smallValue.to!string ~ " is greater than or equal to " ~ smallValue.to!string ~ "."); 42 | msg.split("\n")[2].strip.should.equal("Expected:less than " ~ smallValue.to!string); 43 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.to!string); 44 | }); 45 | 46 | it("should throw a detailed error when the negated comparison fails", { 47 | auto msg = ({ 48 | expect(smallValue).not.to.be.lessThan(largeValue); 49 | }).should.throwException!TestException.msg; 50 | 51 | msg.split("\n")[0].should.equal(smallValue.to!string ~ " should not be less than " ~ largeValue.to!string ~ ". " ~ smallValue.to!string ~ " is less than " ~ largeValue.to!string ~ "."); 52 | msg.split("\n")[2].strip.should.equal("Expected:greater than or equal to " ~ largeValue.to!string); 53 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.to!string); 54 | }); 55 | }); 56 | } 57 | 58 | describe("using Duration values", { 59 | Duration smallValue; 60 | Duration largeValue; 61 | 62 | before({ 63 | smallValue = 40.seconds; 64 | largeValue = 41.seconds; 65 | }); 66 | 67 | it("should be able to compare two values", { 68 | expect(smallValue).to.be.lessThan(largeValue); 69 | expect(smallValue).to.be.below(largeValue); 70 | }); 71 | 72 | it("should be able to compare two values using negation", { 73 | expect(largeValue).not.to.be.lessThan(smallValue); 74 | expect(largeValue).not.to.be.below(smallValue); 75 | }); 76 | 77 | it("should throw a detailed error when the number is compared with itself", { 78 | auto msg = ({ 79 | expect(smallValue).to.be.lessThan(smallValue); 80 | }).should.throwException!TestException.msg; 81 | 82 | msg.split("\n")[0].should.equal(smallValue.to!string ~ " should be less than " ~ smallValue.to!string ~ ". " ~ smallValue.to!string ~ " is greater than or equal to " ~ smallValue.to!string ~ "."); 83 | msg.split("\n")[2].strip.should.equal("Expected:less than " ~ smallValue.to!string); 84 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.to!string); 85 | }); 86 | 87 | it("should throw a detailed error when the negated comparison fails", { 88 | auto msg = ({ 89 | expect(smallValue).not.to.be.lessThan(largeValue); 90 | }).should.throwException!TestException.msg; 91 | 92 | msg.split("\n")[0].should.equal(smallValue.to!string ~ " should not be less than " ~ largeValue.to!string ~ ". " ~ smallValue.to!string ~ " is less than " ~ largeValue.to!string ~ "."); 93 | msg.split("\n")[2].strip.should.equal("Expected:greater than or equal to " ~ largeValue.to!string); 94 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.to!string); 95 | }); 96 | }); 97 | 98 | describe("using SysTime values", { 99 | SysTime smallValue; 100 | SysTime largeValue; 101 | 102 | before({ 103 | smallValue = Clock.currTime; 104 | largeValue = smallValue + 4.seconds; 105 | }); 106 | 107 | it("should be able to compare two values", { 108 | expect(smallValue).to.be.lessThan(largeValue); 109 | expect(smallValue).to.be.below(largeValue); 110 | }); 111 | 112 | it("should be able to compare two values using negation", { 113 | expect(largeValue).not.to.be.lessThan(smallValue); 114 | expect(largeValue).not.to.be.below(smallValue); 115 | }); 116 | 117 | it("should throw a detailed error when the number is compared with itself", { 118 | auto msg = ({ 119 | expect(smallValue).to.be.lessThan(smallValue); 120 | }).should.throwException!TestException.msg; 121 | 122 | msg.split("\n")[0].should.equal(smallValue.toISOExtString ~ " should be less than " ~ smallValue.toISOExtString ~ ". " ~ smallValue.toISOExtString ~ " is greater than or equal to " ~ smallValue.toISOExtString ~ "."); 123 | msg.split("\n")[2].strip.should.equal("Expected:less than " ~ smallValue.toISOExtString); 124 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.toISOExtString); 125 | }); 126 | 127 | it("should throw a detailed error when the negated comparison fails", { 128 | auto msg = ({ 129 | expect(smallValue).not.to.be.lessThan(largeValue); 130 | }).should.throwException!TestException.msg; 131 | 132 | msg.split("\n")[0].should.equal(smallValue.toISOExtString ~ " should not be less than " ~ largeValue.toISOExtString ~ ". " ~ smallValue.toISOExtString ~ " is less than " ~ largeValue.toISOExtString ~ "."); 133 | msg.split("\n")[2].strip.should.equal("Expected:greater than or equal to " ~ largeValue.toISOExtString); 134 | msg.split("\n")[3].strip.should.equal("Actual:" ~ smallValue.toISOExtString); 135 | }); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /test/operations/startWith.d: -------------------------------------------------------------------------------- 1 | module test.operations.startWith; 2 | 3 | import fluentasserts.core.expect; 4 | import fluent.asserts; 5 | 6 | import trial.discovery.spec; 7 | 8 | import std.string; 9 | import std.conv; 10 | import std.meta; 11 | 12 | alias s = Spec!({ 13 | 14 | alias StringTypes = AliasSeq!(string, wstring, dstring); 15 | 16 | static foreach(Type; StringTypes) { 17 | describe("using " ~ Type.stringof ~ " values", { 18 | Type testValue; 19 | 20 | before({ 21 | testValue = "test string".to!Type; 22 | }); 23 | 24 | it("should check that a string starts with a certain substring", { 25 | expect(testValue).to.startWith("test"); 26 | }); 27 | 28 | it("should check that a string starts with a certain char", { 29 | expect(testValue).to.startWith('t'); 30 | }); 31 | 32 | it("should check that a string does not start with a certain substring", { 33 | expect(testValue).to.not.startWith("other"); 34 | }); 35 | 36 | it("should check that a string does not start with a certain char", { 37 | expect(testValue).to.not.startWith('o'); 38 | }); 39 | 40 | it("should throw a detailed error when the string does not start with the substring what was expected", { 41 | auto msg = ({ 42 | expect(testValue).to.startWith("other"); 43 | }).should.throwException!TestException.msg; 44 | 45 | msg.split("\n")[0].should.contain(`"test string" should start with "other". "test string" does not start with "other".`); 46 | msg.split("\n")[2].strip.should.equal(`Expected:to start with "other"`); 47 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 48 | }); 49 | 50 | it("should throw a detailed error when the string does not start with the char what was expected", { 51 | auto msg = ({ 52 | expect(testValue).to.startWith('o'); 53 | }).should.throwException!TestException.msg; 54 | 55 | msg.split("\n")[0].should.contain(`"test string" should start with 'o'. "test string" does not start with 'o'.`); 56 | msg.split("\n")[2].strip.should.equal(`Expected:to start with 'o'`); 57 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 58 | }); 59 | 60 | it("should throw a detailed error when the string does start with the unexpected substring", { 61 | auto msg = ({ 62 | expect(testValue).to.not.startWith("test"); 63 | }).should.throwException!TestException.msg; 64 | 65 | msg.split("\n")[0].should.contain(`"test string" should not start with "test". "test string" starts with "test".`); 66 | msg.split("\n")[2].strip.should.equal(`Expected:to not start with "test"`); 67 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 68 | }); 69 | 70 | it("should throw a detailed error when the string does start with the unexpected char", { 71 | auto msg = ({ 72 | expect(testValue).to.not.startWith('t'); 73 | }).should.throwException!TestException.msg; 74 | 75 | msg.split("\n")[0].should.contain(`"test string" should not start with 't'. "test string" starts with 't'.`); 76 | msg.split("\n")[2].strip.should.equal(`Expected:to not start with 't'`); 77 | msg.split("\n")[3].strip.should.equal(`Actual:"test string"`); 78 | }); 79 | }); 80 | } 81 | }); 82 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | /+dub.sdl: 2 | dependency "fluent-asserts" version= "~>0.13.3" 3 | +/ 4 | 5 | import std.stdio; 6 | import fluent.asserts; 7 | 8 | void f2() { 9 | int k = 3; 10 | Assert.equal(k, 4); 11 | } 12 | 13 | void f1() { 14 | int j = 2; 15 | f2(); 16 | } 17 | 18 | void f0() { 19 | int i = 1; 20 | f1(); 21 | } 22 | 23 | void main() { 24 | f0(); 25 | } -------------------------------------------------------------------------------- /test/unit-threaded/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | __test__*__ 7 | -------------------------------------------------------------------------------- /test/unit-threaded/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unit-threaded-example", 3 | "authors": [ 4 | "Szabo Bogdan" 5 | ], 6 | "description": "A minimal D application.", 7 | "copyright": "Copyright © 2017, Szabo Bogdan", 8 | "license": "MIT", 9 | 10 | "dependencies": { 11 | "fluent-asserts": { 12 | "path": "../../" 13 | }, 14 | "unit-threaded": "*" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/unit-threaded/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import std.traits; 3 | 4 | import fluent.asserts; 5 | import unit_threaded.should : UnitTestException; 6 | 7 | int main() 8 | { 9 | pragma(msg, "base classes:", BaseTypeTuple!TestException); 10 | 11 | try { 12 | 0.should.equal(1); 13 | } catch (UnitTestException e) { 14 | writeln("Got the right exception"); 15 | return 0; 16 | } catch(Throwable t) { 17 | t.writeln; 18 | } 19 | 20 | return 1; 21 | } 22 | -------------------------------------------------------------------------------- /test/values.d: -------------------------------------------------------------------------------- 1 | unittest { 2 | [1, 2, 3] 3 | .should 4 | .contain(4); 5 | } 6 | 7 | unittest { 8 | auto a = 1; 9 | 10 | [1, 2, 3] 11 | .should 12 | .contain(4); 13 | } 14 | 15 | unittest { 16 | /**/ 17 | 18 | [1, 2, 3] 19 | .should 20 | .contain(4); 21 | } 22 | 23 | unittest { 24 | /++/ 25 | 26 | [1, 2, 3] 27 | .should 28 | .contain(4); 29 | } 30 | 31 | unittest { 32 | //some comment 33 | 34 | [1, 2, 3] 35 | .should 36 | .contain(4); 37 | } 38 | 39 | 40 | unittest { 41 | /* 42 | Multi line comment 43 | */ 44 | 45 | `multi 46 | line 47 | string` 48 | .should 49 | .contain(`multi 50 | line 51 | string`); 52 | } 53 | 54 | unittest { 55 | Assert.equal(5, 6); 56 | Assert.notEqual((5+1), 5); 57 | Assert.equal((5, (11))); 58 | } 59 | 60 | unittest { 61 | 5.should.equal(6); 62 | (5+1).should.equal(5); 63 | (5, (11)).should.equal(3); 64 | } 65 | 66 | unittest { 67 | foreach(value; array) { 68 | 69 | } 70 | 71 | found.should.equal(1); 72 | } 73 | 74 | unittest { 75 | found(4).should.equal(1); 76 | } 77 | 78 | unittest { 79 | ({ 80 | ({ }).should.beNull; 81 | }).should.throwException!TestException.msg; 82 | } 83 | 84 | unittest { 85 | [1, 2, 3].map!"a".should.throwException!TestException.msg; 86 | } 87 | 88 | unittest { 89 | Assert.equal([ new Value(1), new Value(2) ], [1, 3]); 90 | [ new Value(1), new Value(2) ].should.equal([1, 2]); 91 | } 92 | 93 | unittest { 94 | describe("when there are 2 android devices and one is not healthy", { 95 | MockDevice device1; 96 | MockDevice device2; 97 | 98 | it("should throw an exception if we request 2 android devices", { 99 | ({ 100 | auto result = [ device1.idup, device2.idup ].filterBy(RunOptions("", "android", 2)).array; 101 | }).should.throwException!DeviceException.withMessage.equal("You requested 2 `androdid` devices, but there is only 1 healthy."); 102 | }); 103 | }); 104 | } 105 | 106 | -------------------------------------------------------------------------------- /test/vibe-0.8/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vibe-example", 3 | "authors": [ 4 | "Bogdan Szabo" 5 | ], 6 | "description": "A minimal D application.", 7 | "copyright": "Copyright © 2018, Bogdan Szabo", 8 | "license": "MIT", 9 | "dependencies": { 10 | "vibe-d:core": "~>0.8.0", 11 | "vibe-d:redis": "~>0.8.0", 12 | "vibe-d:data": "~>0.8.0", 13 | "vibe-d:http": "~>0.8.0", 14 | "fluent-asserts": { 15 | "path": "../../" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /test/vibe-0.8/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | 3 | void main() 4 | { 5 | writeln("Edit source/app.d to start your project."); 6 | } 7 | -------------------------------------------------------------------------------- /travis-ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x -o pipefail 4 | 5 | # test for successful 32-bit build 6 | if [ "$DC" == "dmd" ]; then 7 | dub build --combined --arch=x86 8 | dub clean --all-packages 9 | fi 10 | 11 | # test for successful release build 12 | dub build --combined -b release --compiler=$DC 13 | dub clean --all-packages 14 | 15 | # build Trial 16 | git clone https://github.com/gedaiu/trial.git 17 | cd trial 18 | dub build :runner 19 | cd .. 20 | 21 | # run unit tests 22 | ./trial/trial --compiler=$DC 23 | 24 | # run a build for unit-threaded 25 | dub --root=test/unit-threaded --compiler=$DC --arch=x86_64 26 | 27 | # run a build for vibe-d 0.8 28 | if [[ ${DC=dmd} = dmd ]]; then 29 | dub -v --root=test/vibe-0.8 --compiler=$DC --arch=x86_64 30 | fi 31 | -------------------------------------------------------------------------------- /trial.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporters": [ 3 | "spec", 4 | "result", 5 | "xunit" 6 | ], 7 | "executor": "default", 8 | "plugins": [], 9 | "artifactsLocation": ".trial", 10 | "warningTestDuration": 20, 11 | "maxThreads": 0, 12 | "dangerTestDuration": 100, 13 | "testDiscovery": [ 14 | "trial.discovery.unit.UnitTestDiscovery", 15 | "trial.discovery.spec.SpecTestDiscovery" 16 | ] 17 | } --------------------------------------------------------------------------------