├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── deps.edn ├── package.fig ├── project.clj ├── scripts ├── repl ├── repl.clj └── run-tests.js ├── src ├── cljc │ ├── expectations.cljc │ └── expectations │ │ └── platform.cljc ├── cljs │ └── expectations │ │ └── cljs.clj ├── clojure │ └── expectations │ │ └── junit │ │ └── runner.clj └── java │ └── expectations │ └── junit │ ├── ExpectationsFailure.java │ └── ExpectationsTestRunner.java └── test ├── cljc ├── expectations │ └── test.cljc └── success │ ├── expectations_options.cljc │ ├── nested │ └── success_examples.cljc │ ├── success_examples.cljc │ └── success_examples_src.cljc ├── clojure └── failure │ ├── failure_examples.clj │ └── not.clj.txt ├── expectations └── expectations_expectations.clj └── java ├── FailureTest.java └── SuccessTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /lib 9 | /out 10 | .fig 11 | .cpcache 12 | /*nrepl-server* 13 | /repl-port 14 | /.lein-* 15 | /.nrepl-port 16 | /node_modules 17 | /*-init.clj 18 | /.idea 19 | *.iml 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes coming in version 2.1.11 2 | 3 | `(expect ::some-spec (some-expr))` will test the result of `(some-expr)` against `::some-spec` using `clojure.spec` -- if `clojure.spec` is available and `::some-spec` is registered as a spec (Clojure-only). 4 | 5 | # Changes in version 2.1.10 6 | 7 | Add `approximately` predicate, to test if two floating point values are "equal" (within a given tolerance) #84. 8 | 9 | Add `functionally` predicate, to test if two functions are "functionally equivalent" #88. 10 | 11 | Numerous cljc platform bug fixes; documented testing process; updated change log (was stuck at 2.1.4). 12 | 13 | # Changes in version 2.1.9 14 | 15 | Allow `more-of` to expect an exception type #80. 16 | 17 | # Changes in version 2.1.5-2.1.8 18 | 19 | Switched from cljx to cljc (and fix various bugs that caused). 20 | 21 | Improve handling of null values with regex expectations #75. 22 | 23 | # Changes in version 2.1.4 24 | 25 | Remove expect-let and expect-let focused. 26 | 27 | # Changes in version 2.1.3 28 | 29 | Minor update to move CustomPred checks in front of all other checking. 30 | 31 | # Changes in version 2.1.2 32 | 33 | Minor update to allow redef-state to work with private vars. 34 | 35 | # Changes in version 2.1.1 36 | 37 | ## ClojureScript support 38 | 39 | Make Expectations work with recent ClojureScript versions (> 0.0-2985). 40 | 41 | Since [r2985](https://github.com/clojure/clojurescript/releases/tag/r2985) ClojureScript introduced macro symbols 42 | in its analysis map, and Expectations tried to use them to construct vars, hence the failure described in 43 | [a comment to #51](https://github.com/jaycfields/expectations/pull/51#issuecomment-83922145). This is now fixed. 44 | 45 | # Changes in version 2.1.0 46 | 47 | ## ClojureScript support 48 | 49 | This release adds support for ClojureScript. You can write your tests the same 50 | as in Clojure, except for the usual ClojureScript quirks. 51 | 52 | ### Running your tests 53 | 54 | Your tests written in ClojureScript should be compiled to JS and run by a JavaScript 55 | runtime (such as Node.js, Phantom.js, your browser or whatnot). `lein-cljsbuild` is 56 | the Leiningen plugin that does that for you. 57 | 58 | Here's a sample `project.clj` excerpt: 59 | 60 | ```clojure 61 | :profiles {:dev {:node-dependencies [[source-map-support "^0.2.9"]] 62 | :plugins [[lein-cljsbuild "1.0.5"] 63 | [lein-npm "0.5.0"]]}} 64 | :cljsbuild {:builds [{:id "test" ;; your build config name 65 | :source-paths ["src/cljs" "test/cljs"] ;; your source and test dirs 66 | :notify-command ["node" "./out/tests/test.js"] ;; `node ./out/tests/test.js` to be 67 | ;; run automatically after compile 68 | :compiler {:target :nodejs ;; use this if you want to run on Node.js 69 | :main my-lib.test ;; your tests main namespace 70 | :output-to "out/tests/test.js" ;; compiled JS main file 71 | :output-dir "out/tests" ;; compiled JS dir 72 | :optimizations :none ;; maybe just leave as it is ;) 73 | :cache-analysis true 74 | :source-map true 75 | :pretty-print true}}]} 76 | ``` 77 | 78 | To compile and run your tests just once, run 79 | 80 | lein cljsbuild once test 81 | 82 | To run automatic incremental compilation and testing your changes, run 83 | 84 | lein cljsbuild auto test 85 | 86 | #### Tests main namespace (entry point) 87 | 88 | Your tests main namespace should require `expectations.cljs` to be able to run your tests: 89 | 90 | ```clojure 91 | (ns my-lib.test 92 | (:require-macros [expectations.cljs :as ecljs]) 93 | (:require [expectations] 94 | [my-lib.expectations-options] 95 | [my-lib.core-test] 96 | [my-lib.util-test])) 97 | 98 | (defn -main [] 99 | (ecljs/run-all-tests)) 100 | 101 | (enable-console-print!) 102 | (set! *main-cli-fn* -main) 103 | ``` 104 | 105 | You should use `run-all-tests` macro: 106 | 107 | (expectations.cljs/run-all-tests) 108 | 109 | or `run-tests`, like this: 110 | 111 | (expectations.cljs/run-tests my-lib.core-test my-lib.util-test my-other.namespaces) 112 | 113 | 114 | #### Requiring the `expectations` namespace 115 | 116 | A usual tiny difference from Clojure. 117 | 118 | Clojure: 119 | 120 | ``` 121 | (ns success.nested.success-examples 122 | (:require [expectations :refer :all])) 123 | ``` 124 | 125 | ClojureScript: 126 | 127 | ``` 128 | (ns success.nested.success-examples 129 | (:require-macros [expectations :refer [expect 130 | expect-focused 131 | ;; etc. 132 | ]])) 133 | ``` 134 | 135 | ### Implementation notes 136 | 137 | `pprint` is not supported in ClojureScript yet, so datastructures will not get pretty printed in failed test reports. 138 | 139 | `freeze-time` macro is not yet implemented for ClojureScript. 140 | 141 | JavaScript error stack traces will look a bit verbose as we're not eliding system and `expectations` files just yet. 142 | 143 | ### Maintainer notes 144 | 145 | This version uses [cljx](https://github.com/lynaghk/cljx) to add support for ClojureScript. 146 | 147 | Use `lein test` to run Expectations' internal tests. 148 | Currently only _success-examples_ tests have been migrated. 149 | 150 | # Changes to expectations in Version 2.0 151 | 152 | ## CONTENTS 153 | 154 | ## 1 new and improved features 155 | 156 | ### 1.1 side-effects 157 | 158 | expectations 2.0 introduces the 'side-effects macro, which allows you to 159 | capture arguments to functions you specify. Previous to version 2.0 160 | behavior was often tested using the 'interaction macro. expectations 2.0 161 | removes the 'interaction macro and embraces the idea of verifying 162 | interactions at the data level, using the same type of comparison that 163 | is used for all other data. 164 | 165 | Examples: 166 | ```clojure 167 | (expect [["/tmp/hello-world" "some data" :append true] 168 | ["/tmp/hello-world" "some data" :append true]] 169 | (side-effects [spit] 170 | (spit "/tmp/hello-world" "some data" :append true) 171 | (spit "/tmp/hello-world" "some data" :append true))) 172 | ``` 173 | In the above example, you specify that spit is a side effect 174 | fn, and the 'side-effects macro will return a list of all calls 175 | made, with the arguments used in the call. The above example 176 | uses simple equality for verification. 177 | ```clojure 178 | (expect empty? 179 | (side-effects [spit] "spit never called")) 180 | ``` 181 | The above example demonstrates how you can use non-equality to 182 | to verify the data returned. 183 | ```clojure 184 | (expect ["/tmp/hello-world" "some data" :append true] 185 | (in (side-effects [spit] 186 | (spit "some other stuff" "xy") 187 | (spit "/tmp/hello-world" "some data" :append true)))) 188 | ``` 189 | Immediately above is an example of combining 'side-effects with 'in 190 | for a more concise test. Here we're testing that the expected data 191 | will exist somewhere within the list returned by 'side-effects 192 | 193 | ### 1.2 more, more->, & more-of 194 | 195 | expectations has always given you the ability to test against an 196 | arbitrary fn, similar to the example below. 197 | ```clojure 198 | (expect nil? nil) 199 | ``` 200 | The ability to specify any fn is powerful, but it doesn't always give 201 | you the most descriptive failure messages. In expectations 2.0 we 202 | introduce the 'more, 'more->, & 'more-of macros, which are designed 203 | to allow you to expect more of your actual values. 204 | 205 | Below is a simple example of using the more macro. 206 | ```clojure 207 | (expect (more vector? not-empty) [1 2 3]) 208 | ``` 209 | As you can see from the above example, we're simply expecting that 210 | the actual value '[1 2 3] is both a 'vector? and 'not-empty. The 211 | 'more macro is great when you want to test a few 1 arg fns; however, 212 | I expect you'll more often find yourself reaching for 'more-> and 213 | 'more-of. 214 | 215 | The 'more-> macro is used for threading the actual value and 216 | comparing the result to an expected value. Below is a simple example 217 | of using 'more to pull values out of a vector and test their equality. 218 | ```clojure 219 | (expect (more-> 1 first 220 | 3 last) 221 | [1 2 3]) 222 | ``` 223 | The 'more-> macro threads using -> (thread-first), so you're able 224 | to put any form you'd like in the actual transformation. 225 | ```clojure 226 | (expect (more-> 2 (-> first (+ 1)) 227 | 3 last) 228 | [1 2 3]) 229 | ``` 230 | Finally, 'more-> can be very helpful for testing various kv pairs 231 | within a map, or various Java fields. 232 | ```clojure 233 | (expect (more-> 0 .size 234 | true .isEmpty) 235 | (java.util.ArrayList.)) 236 | 237 | (expect (more-> 2 :a 238 | 4 :b) 239 | {:a 2 :b 4}) 240 | ``` 241 | Threading is great work, if you can get it. For the times when 242 | you need to name your actual value, 'more-of should do the trick. 243 | The following example demonstrates how to name your actual value 244 | and then specify a few expectations. 245 | ```clojure 246 | (expect (more-of x 247 | vector? x 248 | 1 (first x)) 249 | [1 2 3]) 250 | ``` 251 | If you've ever found yourself wishing you had destructuring in 252 | clojure.test/are or expectations/given, you're not alone. The 253 | good news is, 'more-of supports any destructuring you want to 254 | give it. 255 | ```clojure 256 | (expect (more-of [x :as all] 257 | vector? all 258 | 1 x) 259 | [1 2 3]) 260 | ``` 261 | ### 1.3 combining side-effects and more-of 262 | 263 | It's fairly common to expect some behavior where you know the 264 | exact values for some of the args, and you have something more 265 | general in mind for the additional args. By combining 'side-effects 266 | and 'more-of you can easily destructure a call into it's args and verify 267 | as many as you care to verify. 268 | ```clojure 269 | (expect (more-of [path data action {:keys [a c]}] 270 | String path 271 | #"some da" data 272 | keyword? action 273 | :b a 274 | :d c) 275 | (in (side-effects [spit] 276 | (spit "/tmp/hello-world" "some data" :append {:a :b :c :d :e :f})))) 277 | ``` 278 | The above test is a bit much to swallow at first glance; however, 279 | it's actually very straightforward once you've gotten used to the 280 | 'more-of syntax. In the above example the 'spit fn is called with 281 | the args "/tmp/hello-world", "some data" :append {:a :b :c :d :e :f}. 282 | Using 'more-of, we destructure those args, and expect them 283 | individually. The path arg is expected to be of type String. The 284 | data arg is expected to be a string that matches the regex 285 | "some da". The action is expected to be a 'keyword?. Finally, 286 | the options map is destructured to it's :a and :c values, and 287 | equality expected. 288 | 289 | ### 1.4 from-each 290 | 291 | It's common to expect something from a list of actual values. 292 | Traditionally 'given was used to generate many tests from one 293 | form. Unfortunately 'given suffered from many issues: no 294 | ability to destructure values, failure line numbers were 295 | almost completely useless, and little visibility into what 296 | the problem was when a failure did occur. 297 | 298 | In expectations 2.0 'from-each was introduced to provide 299 | a more powerful syntax as well as more helpful failure 300 | messages. 301 | 302 | Below you can see a very simple expectation that verifies 303 | each of the elements of a vector is a String. 304 | ```clojure 305 | (expect String 306 | (from-each [letter ["a" "b" "c"]] 307 | letter)) 308 | ``` 309 | Hopefully the syntax of 'from-each feels very familiar, it's 310 | been written to handle the same options as 'for and 'doseq - 311 | :let and :when. 312 | ```clojure 313 | (expect odd? (from-each [num [1 2 3] 314 | :when (not= num 2)] 315 | num)) 316 | 317 | (expect odd? (from-each [num [1 2 3] 318 | :when (not= num 2) 319 | :let [numinc1 (inc num)]] 320 | (inc numinc1))) 321 | ``` 322 | While 'from-each is helpful in creating concise tests, I 323 | actually find it's most value when a test fails. If you 324 | take the above test and remove the :when, you would have 325 | the test below. 326 | ```clojure 327 | (expect odd? (from-each [num [1 2 3] 328 | :let [numinc1 (inc num)]] 329 | (inc numinc1))) 330 | ``` 331 | The above test would definitely fail, but it's not 332 | immediately obvious what the issue is. However, the failure 333 | message should quickly lead you to the underlying issue. 334 | 335 | ``` 336 | failure in (success_examples.clj:206) : success.success-examples 337 | (expect 338 | odd? 339 | (from-each [num [1 2 3] :let [numinc1 (inc num)]] (inc numinc1))) 340 | 341 | the list: (3 4 5) 342 | 343 | (expect odd? (inc numinc1)) 344 | 345 | locals num: 2 346 | numinc1: 3 347 | 4 is not odd? 348 | ``` 349 | As you can see above, when 'from-each fails it will give you 350 | values of every var defined within the 'from-each bindings. As 351 | a result, it's fairly easy to find the combination of vars that 352 | led to a failing test. 353 | 354 | ## 2 changed 355 | 356 | ### 2.1 custom diff syntax removed 357 | 358 | expectations was originally written before clojure.data/diff 359 | existed, and defined it's own syntax for printing diffed data 360 | between actual and expected. Now that there's a standard, it 361 | no longer makes sense to define a custom syntax. All error 362 | messages that previously used expectations custom diff syntax 363 | have been converted to simply use clojure.data/diff result maps. 364 | 365 | Below is an example failure message that utilizes 366 | clojure.data/diff for reporting the inconsistencies. 367 | ``` 368 | failure in (failure_examples.clj:23) : failure.failure-examples 369 | (expect 370 | {:foo 1, :bar 3, :dog 3, :car 4} 371 | (assoc {} :foo 1 :bar "3" :cat 4)) 372 | 373 | expected: {:foo 1, :bar 3, :dog 3, :car 4} 374 | was: {:cat 4, :bar "3", :foo 1} 375 | 376 | in expected, not actual: {:car 4, :dog 3, :bar 3} 377 | in actual, not expected: {:cat 4, :bar "3"} 378 | ``` 379 | ## 3 removed 380 | 381 | ### 3.1 given 382 | 383 | As I said above, 'given suffered from many issues: no 384 | ability to destructure values, failure line numbers were 385 | almost completely useless, and little visibility into what 386 | the problem was when a failure did occur. I personally found 387 | given expectations to often be the hardest to maintain, and 388 | often I felt they were completely unmaintainable. 389 | 390 | As a result, 'given has been replaced with 'from-each, 'more, 391 | 'more->, & 'more-of. I've converted a few codebases over, and 392 | I've found the 'more-* or 'from-each post conversion test to 393 | be far more maintainable. 394 | 395 | ### 3.2 function interaction tests 396 | 397 | expectations 2.0 abandons behavior based testing and the 398 | interaction syntax. Existing tests relying on interaction 399 | should be easy to convert to side-effects. 400 | 401 | ### 3.3 java mock interaction tests 402 | 403 | java mock interaction tests never really fit well within 404 | expectations, and the removal of function interaction 405 | tests caused us to remove the java mock support as well. 406 | 407 | ### 3.4 Double/NaN support 408 | 409 | The original project using expectations made heavy usage of 410 | Double/NaN, and the original version of expectations did it's 411 | best to hide the fact that Double/NaN isn't = to Double/NaN. 412 | Unfortunately, over time this hiding became confusing and 413 | complicated to maintain. In expectations 2.0 we gave up on this 414 | fight and removed Double/NaN support. 415 | 416 | If you find yourself fighting the NaN battle, I'd suggest 417 | writing a helper method and simply using that in your tests. 418 | Here are a few [examples](https://gist.github.com/jaycfields/9050825) 419 | 420 | # Changes in version 2.0.10 421 | 422 | ## metadata added to 'work argument used in in-context 423 | 424 | Since version 1.4.36 expectations has allowed you 425 | to alter the context in which 426 | your tests run by creating a function that takes the "run the tests" 427 | function as an arg, and do you as wish. [more info](http://jayfields.com/expectations/in-context.html) 428 | 429 | In version 2.0.10 of expecatations, the "run the tests" function 430 | has metadata that allows you to see the var for the test. 431 | 432 | The following code would allow you to see the metadata of each 433 | test being run. 434 | 435 | ```clj 436 | (defn in-context 437 | {:expectations-options :in-context} 438 | [work] 439 | (println (meta (:the-var (meta work)))) 440 | (work)) 441 | ``` 442 | 443 | # Changes in version 2.0.11 and 2.0.12 444 | 445 | None. Both versions were released as the result of automating deployment. 446 | 447 | # Changes in version 2.0.13 448 | 449 | Tests within a namespace are run in order. 450 | 451 | # Changes from version 2.0.14 - 2.0.16 452 | 453 | Small change allowing filenames that start with integers. The number of releases 454 | was due to tweaking automated deployment (again). 455 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2010-2017, Jay C Fields, Sean Corfield 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the names of the copyright holders nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Expectations 2 | 3 | This is the "classic" expectations library (considered stable and "in maintenance mode" at this point). 4 | It uses its own set of tooling and is **not compatible with `clojure.test`-based tooling.** 5 | 6 | ## `clojure.test` compatibility 7 | 8 | There is a very actively-maintained variant of this library that _is_ compatible with `clojure.test` and its tooling: 9 | 10 | [expectations.clojure.test](https://github.com/clojure-expectations/clojure-test) 11 | ([documentation](https://cljdoc.org/d/com.github.seancorfield/expectations/)) 12 | 13 | ## "Classic" Expectations (legacy) 14 | 15 | Visit this website for all of the "classic" expectation docs: 16 | 17 | https://clojure-expectations.github.io 18 | 19 | Running the tests: 20 | 21 | lein do clean, test 22 | 23 | This will run the (successful) expectations for Clojure (currently 83). 24 | 25 | Then run the ClojureScript tests interactively: 26 | 27 | ./scripts/repl 28 | cljs.user=> (require 'expectations.test) 29 | ... 30 | cljs.user=> (expectations.test/-main) 31 | 32 | This will run the (successful) expectations that are compatible with ClojureScript (currently 69/69). 33 | 34 | You can run _all_ expectations via: 35 | 36 | lein do clean, expectations 37 | 38 | This includes the deliberately failing expectations (used to visually confirm behavior for failing tests) and should run 128 assertions in total, of which 43 will fail and 2 will error. 39 | 40 | ## License & Copyright 41 | 42 | Copyright (c) 2010-2019, Jay C Fields, Sean Corfield. All rights reserved. This software is available under the BSD 3-Clause Open Source License. 43 | 44 | ## Donate to Jay C Fields, the creator of Expectations 45 | 46 | Donate Bitcoins 47 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {joda-time/joda-time {:mvn/version "2.9.3"} 2 | junit/junit {:mvn/version "4.12"}} 3 | :paths ["src/cljc" "src/cljs" "src/clojure"] 4 | :aliases {:test {:extra-paths ["test/cljc" "test/clojure" "test"]}}} 5 | -------------------------------------------------------------------------------- /package.fig: -------------------------------------------------------------------------------- 1 | resource "target/expectations.jar" 2 | 3 | retrieve CLASSPATH->lib/jars 4 | retrieve SOURCEPATH->lib/sources 5 | 6 | config default 7 | append CLASSPATH=@/target/expectations.jar 8 | append SOURCEPATH=@/target/expectations.jar 9 | 10 | include :core_deps 11 | end 12 | 13 | config build 14 | include :core_deps 15 | include joda.time/2.1 16 | end 17 | 18 | config core_deps 19 | include junit/4.7 20 | include mockito/1.8.0 21 | end 22 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject expectations "2.1.10" 2 | :description "a minimalist's unit testing framework" 3 | :license {:name "BSD 3-Clause License" 4 | :url "https://opensource.org/licenses/BSD-3-Clause"} 5 | :url "https://github.com/clojure-expectations/expectations" 6 | :scm {:name "git" 7 | :url "https://github.com/clojure-expectations/expectations"} 8 | :jar-name "expectations.jar" 9 | :jar-exclusions [#"\.swp|\.swo|\.DS_Store"] 10 | :java-source-paths ["src/java"] 11 | :source-paths ["src/cljc" "src/clojure" "src/cljs"] 12 | :test-paths ["test/cljc" "test/clojure" "test"] 13 | 14 | :dependencies [[joda-time/joda-time "2.9.3"] 15 | [junit/junit "4.12"]] 16 | 17 | :plugins [[lein-expectations "0.0.8"] 18 | [lein-publishers "1.0.13"]] 19 | 20 | :deploy-repositories [["releases" :clojars]] 21 | 22 | :profiles {:dev {:dependencies [[org.clojure/clojure "1.8.0"] 23 | [org.clojure/clojurescript "1.8.51" :scope "provided"]] 24 | :node-dependencies [[source-map-support "^0.2.9"]] 25 | :plugins [[lein-cljsbuild "1.1.5"] 26 | [lein-npm "0.6.2"]]}} 27 | 28 | :prep-tasks ["javac"] 29 | :auto-clean false 30 | 31 | :aliases {"test" ["expectations" "expectations.*" "success.*"] 32 | "test-fail" ["expectations" "failure.*"]} 33 | 34 | :cljsbuild {:builds [{:source-paths ["src/cljs" "src/cljc" "test/cljc"] 35 | :notify-command ["node" "./target/out/test.js"] 36 | :compiler {:target :nodejs 37 | :main expectations.test 38 | :output-to "target/out/test.js" 39 | :output-dir "target/out" 40 | :optimizations :none 41 | :cache-analysis true 42 | :source-map true 43 | :pretty-print true}}]} 44 | 45 | :min-lein-version "2.5.0") 46 | -------------------------------------------------------------------------------- /scripts/repl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rlwrap lein trampoline run -m clojure.main scripts/repl.clj 3 | -------------------------------------------------------------------------------- /scripts/repl.clj: -------------------------------------------------------------------------------- 1 | (require 2 | '[cljs.repl :as repl] 3 | '[cljs.repl.node :as node]) 4 | 5 | (repl/repl* (node/repl-env) 6 | {:output-dir "target/out" 7 | :optimizations :none 8 | :cache-analysis true 9 | :source-map true}) 10 | -------------------------------------------------------------------------------- /scripts/run-tests.js: -------------------------------------------------------------------------------- 1 | try { 2 | require("source-map-support").install(); 3 | } catch(err) { 4 | } 5 | require("../target/out/goog/bootstrap/nodejs.js"); 6 | require("../target/out/test.js"); 7 | goog.require("expectations.test"); 8 | goog.require("cljs.nodejscli"); 9 | -------------------------------------------------------------------------------- /src/cljc/expectations.cljc: -------------------------------------------------------------------------------- 1 | (ns expectations 2 | (:refer-clojure :exclude [format ns-name]) 3 | (:require [clojure.data] 4 | [clojure.set :refer [difference]] 5 | [clojure.string] 6 | [expectations.platform :as p :refer [format ns-name]]) 7 | #?(:clj 8 | (:import (clojure.lang Agent Atom Ref) 9 | (java.io FileNotFoundException) 10 | (java.util.regex Pattern) 11 | (org.joda.time DateTimeUtils)))) 12 | 13 | (defn no-op [& _]) 14 | 15 | (defn in [n] {::in n ::in-flag true}) 16 | 17 | ;;; GLOBALS 18 | (def run-tests-on-shutdown (atom true)) 19 | (def warn-on-iref-updates-boolean (atom false)) 20 | 21 | (def ^{:dynamic true} *test-name* nil) 22 | (def ^{:dynamic true} *test-meta* {}) 23 | (def ^{:dynamic true} *test-var* nil) 24 | (def ^{:dynamic true} *prune-stacktrace* true) 25 | 26 | (def ^{:dynamic true} *report-counters* nil) ; bound to a ref of a map in test-ns 27 | 28 | (def initial-report-counters ; used to initialize *report-counters* 29 | {:test 0, :pass 0, :fail 0, :error 0 :run-time 0}) 30 | 31 | (def ^{:dynamic true} reminder nil) 32 | 33 | ;;; UTILITIES FOR REPORTING FUNCTIONS 34 | (defn show-raw-choice [] 35 | (if-let [choice (p/getenv "EXPECTATIONS_SHOW_RAW")] 36 | (= "TRUE" (clojure.string/upper-case choice)) 37 | true)) 38 | 39 | (defn colorize-choice [] 40 | (clojure.string/upper-case (or (p/getenv "EXPECTATIONS_COLORIZE") 41 | (str (not (p/on-windows?)))))) 42 | 43 | (def ansi-colors {:reset "[0m" 44 | :red "[31m" 45 | :blue "[34m" 46 | :yellow "[33m" 47 | :cyan "[36m" 48 | :green "[32m" 49 | :magenta "[35m"}) 50 | 51 | (defn ansi [code] 52 | (str \u001b (get ansi-colors code (:reset ansi-colors)))) 53 | 54 | (defn color [code & s] 55 | (str (ansi code) (apply str s) (ansi :reset))) 56 | 57 | (defn colorize-filename [s] 58 | (condp = (colorize-choice) 59 | "TRUE" (color :magenta s) 60 | s)) 61 | 62 | (defn colorize-raw [s] 63 | (condp = (colorize-choice) 64 | "TRUE" (color :cyan s) 65 | s)) 66 | 67 | (defn colorize-results [pred s] 68 | (condp = (colorize-choice) 69 | "TRUE" (if (pred) 70 | (color :green s) 71 | (color :red s)) 72 | s)) 73 | 74 | (defn colorize-warn [s] 75 | (condp = (colorize-choice) 76 | "TRUE" (color :yellow s) 77 | s)) 78 | 79 | (defn string-join [s coll] 80 | (clojure.string/join s (remove nil? coll))) 81 | 82 | (defn- inc-counter! [counters name] 83 | (assoc counters name (inc (or (counters name) 0)))) 84 | 85 | (defn inc-report-counter [name] 86 | (when *report-counters* 87 | (swap! *report-counters* inc-counter! name))) 88 | 89 | ;;; TEST RESULT REPORTING 90 | (defn test-name [{:keys [line ns]}] 91 | (str ns ":" line)) 92 | 93 | (defn test-file [{:keys [file line]}] 94 | (colorize-filename (str (last (re-seq #"[0-9A-Za-z_\.]+" file)) ":" line))) 95 | 96 | (defn raw-str [[e a]] 97 | (with-out-str (p/pprint `(~'expect ~e ~a)))) 98 | 99 | (defn pp-str [e] 100 | (clojure.string/trim (with-out-str (p/pprint e)))) 101 | 102 | (defn ^{:dynamic true} fail [test-name test-meta msg] 103 | (println (str "\nfailure in (" (test-file test-meta) ") : " (:ns test-meta))) (println msg)) 104 | 105 | (defn ^{:dynamic true} summary [msg] (println msg)) 106 | (defn ^{:dynamic true} started [test-name test-meta]) 107 | (defn ^{:dynamic true} finished [test-name test-meta]) 108 | (defn ^{:dynamic true} ns-finished [a-ns]) 109 | (defn ^{:dynamic true} expectation-finished [a-var]) 110 | 111 | (defn ^{:dynamic true} ignored-fns [{:keys [className fileName]}] 112 | (when *prune-stacktrace* 113 | (or (= fileName "expectations.clj") 114 | (= fileName "expectations_options.clj") 115 | (= fileName "NO_SOURCE_FILE") 116 | (= fileName "interruptible_eval.clj") 117 | (re-seq #"clojure\.lang" className) 118 | (re-seq #"clojure\.core" className) 119 | (re-seq #"clojure\.main" className) 120 | (re-seq #"java\.lang" className) 121 | (re-seq #"java\.util\.concurrent\.ThreadPoolExecutor\$Worker" className)))) 122 | 123 | (defn- stackline->str [{:keys [className methodName fileName lineNumber]}] 124 | (if (= methodName "invoke") 125 | (str " on (" fileName ":" lineNumber ")") 126 | (str " " className "$" methodName " (" fileName ":" lineNumber ")"))) 127 | 128 | (defn pruned-stack-trace [t] 129 | #?(:clj (string-join "\n" 130 | (distinct (map stackline->str 131 | (remove ignored-fns (map bean (.getStackTrace t)))))) 132 | :cljs (.-stack t))) ;TODO: proper impl for cljs 133 | 134 | (defn ->failure-message [{:keys [raw ref-data result expected-message actual-message message list show-raw]}] 135 | (string-join "\n" 136 | [(when reminder 137 | (colorize-warn (str " ***** " 138 | (clojure.string/upper-case reminder) 139 | " *****"))) 140 | (when raw (when (or show-raw (show-raw-choice)) (colorize-raw (raw-str raw)))) 141 | (when-let [[n1 v1 & _] ref-data] 142 | (format " locals %s: %s" n1 (pr-str v1))) 143 | (when-let [[_ _ & the-rest] ref-data] 144 | (when the-rest 145 | (->> the-rest 146 | (partition 2) 147 | (map #(format " %s: %s" (first %) (pr-str (second %)))) 148 | (string-join "\n")))) 149 | (when result (str " " (string-join " " result))) 150 | (when (and result (or expected-message actual-message message)) "") 151 | (when expected-message (str " " expected-message)) 152 | (when actual-message (str " " actual-message)) 153 | (when message (str " " message)) 154 | (when list 155 | (str "\n" (string-join "\n\n" 156 | (map ->failure-message list))))])) 157 | 158 | (defmulti report :type) 159 | 160 | (defmethod report :pass [m] 161 | (alter-meta! *test-var* assoc ::run true :status [:success "" (:line *test-meta*)]) 162 | (inc-report-counter :pass)) 163 | 164 | (defmethod report :fail [m] 165 | (inc-report-counter :fail) 166 | (let [current-test *test-var* 167 | message (->failure-message m)] 168 | (alter-meta! current-test assoc ::run true :status [:fail message (:line *test-meta*)]) 169 | (fail *test-name* *test-meta* message))) 170 | 171 | (defmethod report :error [{:keys [result raw] :as m}] 172 | (inc-report-counter :error) 173 | (let [result (first result) 174 | current-test *test-var* 175 | message (string-join "\n" 176 | [(when reminder (colorize-warn (str " ***** " (clojure.string/upper-case reminder) " *****"))) 177 | (when raw 178 | (when (show-raw-choice) (colorize-raw (raw-str raw)))) 179 | (when-let [msg (:expected-message m)] (str " exp-msg: " msg)) 180 | (when-let [msg (:actual-message m)] (str " act-msg: " msg)) 181 | (str " threw: " (type result) " - " (p/get-message result)) 182 | (pruned-stack-trace result)])] 183 | (alter-meta! current-test 184 | assoc ::run true :status [:error message (:line *test-meta*)]) 185 | (fail *test-name* *test-meta* message))) 186 | 187 | (defmethod report :summary [{:keys [test pass fail error run-time ignored-expectations]}] 188 | (summary (str "\nRan " test " tests containing " 189 | (+ pass fail error) " assertions in " 190 | run-time " msecs\n" 191 | (when (> ignored-expectations 0) (colorize-warn (str "IGNORED " ignored-expectations " EXPECTATIONS\n"))) 192 | (colorize-results (partial = 0 fail error) (str fail " failures, " error " errors")) "."))) 193 | 194 | ;; TEST RUNNING 195 | 196 | (defn disable-run-on-shutdown [] (reset! run-tests-on-shutdown false)) 197 | (defn warn-on-iref-updates [] (reset! warn-on-iref-updates-boolean true)) 198 | 199 | (defn add-watch-every-iref-for-updates [iref-vars] 200 | (doseq [var iref-vars] 201 | (add-watch @var ::expectations-watching-state-modifications 202 | (fn [_ reference old-state new-state] 203 | (println (colorize-warn 204 | (clojure.string/join " " 205 | ["WARNING:" 206 | (or *test-name* "test name unset") 207 | "modified" var 208 | "from" (pr-str old-state) 209 | "to" (pr-str new-state)]))) 210 | (when-not *test-name* 211 | (p/print-stack-trace (-> "stacktrace for var modification" 212 | #?(:clj RuntimeException. 213 | :cljs js/Error.)))))))) 214 | 215 | (defn remove-watch-every-iref-for-updates [iref-vars] 216 | (doseq [var iref-vars] 217 | (remove-watch @var ::expectations-watching-state-modifications))) 218 | 219 | (defn test-var [v] 220 | (when-let [t @v] 221 | (let [tn (test-name (meta v)) 222 | tm (meta v)] 223 | (started tn tm) 224 | (inc-report-counter :test) 225 | (binding [*test-name* tn 226 | *test-meta* tm 227 | *test-var* v] 228 | (try 229 | (t) 230 | (catch #?(:clj Throwable 231 | :cljs js/Error) e 232 | (println "\nunexpected error in" tn) 233 | (p/print-stack-trace e)))) 234 | (finished tn tm)))) 235 | 236 | (defn execute-vars [vars] 237 | (doseq [var vars] 238 | (when (p/bound? var) 239 | (when-let [vv @var] 240 | (vv))))) 241 | 242 | (defn create-context [in-context-vars work] 243 | (case (count in-context-vars) 244 | 0 (work) 245 | 1 (@(first in-context-vars) work) 246 | (do 247 | (println "expectations only supports 0 or 1 :in-context fns. Ignoring:" in-context-vars) 248 | (work)))) 249 | 250 | (defn test-vars [vars-by-kind ignored-expectations] 251 | #?(:clj (remove-ns 'expectations-options)) 252 | #?(:clj (try 253 | (require 'expectations-options :reload) 254 | (catch FileNotFoundException e))) 255 | (execute-vars (:before-run vars-by-kind)) 256 | (when @warn-on-iref-updates-boolean 257 | (add-watch-every-iref-for-updates (:iref vars-by-kind))) 258 | (binding [*report-counters* (atom initial-report-counters)] 259 | (let [ns->vars (group-by (comp :ns meta) (sort-by (comp :line meta) (:expectation vars-by-kind))) 260 | start (p/nano-time) 261 | in-context-vars (vec (:in-context vars-by-kind))] 262 | (doseq [[a-ns the-vars] ns->vars] 263 | (doseq [v the-vars] 264 | (create-context in-context-vars ^{:the-var v} #(test-var v)) 265 | (expectation-finished v)) 266 | (ns-finished (ns-name a-ns))) 267 | (let [result (assoc @*report-counters* 268 | :run-time (int (/ (- (p/nano-time) start) 1000000)) 269 | :ignored-expectations ignored-expectations)] 270 | (when @warn-on-iref-updates-boolean 271 | (remove-watch-every-iref-for-updates (:iref vars-by-kind))) 272 | (execute-vars (:after-run vars-by-kind)) 273 | result)))) 274 | 275 | (defn run-tests-in-vars [vars-by-kind] 276 | (doto (assoc (test-vars vars-by-kind 0) :type :summary) 277 | (report))) 278 | 279 | #?(:clj 280 | (defn ->vars [ns] 281 | (->> ns 282 | ns-name 283 | ns-interns 284 | vals 285 | (sort-by str)))) 286 | 287 | (defn var-kind [v] 288 | (let [m (meta v)] 289 | (cond (and (:focused m) 290 | (:expectation m)) :focused 291 | (:expectation m) :expectation 292 | (:expectations-options m) (:expectations-options m) 293 | (p/iref-types (type @v)) :iref))) 294 | 295 | (defn by-kind [vars] 296 | (->> vars 297 | (filter (comp not ::run meta)) 298 | (filter (comp not nil? var-kind)) 299 | (group-by var-kind))) 300 | 301 | #?(:clj 302 | (defn run-tests [namespaces] 303 | (let [vars-by-kind (by-kind (mapcat ->vars namespaces)) 304 | expectations (:expectation vars-by-kind)] 305 | (if-let [focused (:focused vars-by-kind)] 306 | (doto (assoc (test-vars (assoc vars-by-kind :expectation focused) (- (count expectations) (count focused))) 307 | :type :summary) 308 | (report)) 309 | (doto (assoc (test-vars vars-by-kind 0) 310 | :type :summary) 311 | (report)))))) 312 | 313 | #?(:clj 314 | (defn run-all-tests 315 | ([] (run-tests (all-ns))) 316 | ([re] (run-tests (filter #(re-matches re (name (ns-name %))) (all-ns)))))) 317 | 318 | (defprotocol CustomPred 319 | (expect-fn [e a]) 320 | (expected-message [e a str-e str-a]) 321 | (actual-message [e a str-e str-a]) 322 | (message [e a str-e str-a])) 323 | 324 | #?(:clj 325 | (defn- spec? [e] 326 | (and (keyword? e) 327 | (try 328 | (require 'clojure.spec.alpha) 329 | (when-let [get-spec (resolve 'clojure.spec.alpha/get-spec)] 330 | (get-spec e)) 331 | (catch Throwable _)))) 332 | :cljs ; spec testing is not supported on cljs yet 333 | (defn- spec? [_] false)) 334 | 335 | (defmulti compare-expr (fn [e a _ _] 336 | (cond 337 | (and (map? a) (not (sorted? a)) (contains? a ::from-each-flag)) ::from-each 338 | (and (map? a) (not (sorted? a)) (contains? a ::in-flag)) ::in 339 | (satisfies? CustomPred e) ::custom-pred 340 | (and (map? e) (not (sorted? e)) (contains? e ::more)) ::more 341 | (= e a) ::equals 342 | (and (string? e) (string? a)) ::strings 343 | (and (map? e) (map? a)) ::maps 344 | (and (set? e) (set? a)) ::sets 345 | (and (sequential? e) (sequential? a)) ::sequentials 346 | (and (instance? #?(:clj Pattern :cljs js/RegExp) e) 347 | (instance? #?(:clj Pattern :cljs js/RegExp) a)) ::regexps 348 | (instance? #?(:clj Pattern :cljs js/RegExp) e) ::re-seq 349 | (isa? e #?(:clj Throwable :cljs js/Error)) ::expect-exception 350 | (instance? #?(:clj Throwable :cljs js/Error) e) ::expected-exception 351 | (instance? #?(:clj Throwable :cljs js/Error) a) ::actual-exception 352 | (and (instance? #?(:clj Class :cljs js/Function) e) 353 | (instance? #?(:clj Class :cljs js/Function) a)) ::types 354 | (and (instance? #?(:clj Class :cljs js/Function) e) 355 | (not (and (fn? e) (e a)))) ::expect-instance 356 | (spec? e) ::spec 357 | (fn? e) ::fn 358 | :default ::default))) 359 | 360 | (defmethod compare-expr ::equals [e a str-e str-a] 361 | {:type :pass}) 362 | 363 | (defmethod compare-expr ::default [e a str-e str-a] 364 | {:type :fail :raw [str-e str-a] 365 | :result ["expected:" (pr-str e) 366 | "\n was:" (pr-str a)]}) 367 | 368 | (defmethod compare-expr ::custom-pred [e a str-e str-a] 369 | (if (expect-fn e a) 370 | {:type :pass} 371 | {:type :fail 372 | :raw [str-e str-a] 373 | :expected-message (expected-message e a str-e str-a) 374 | :actual-message (actual-message e a str-e str-a) 375 | :message (message e a str-e str-a)})) 376 | 377 | (defmethod compare-expr ::fn [e a str-e str-a] 378 | (try 379 | (if (e a) 380 | {:type :pass} 381 | {:type :fail :raw [str-e str-a] :result [(pr-str a) "is not" str-e]}) 382 | (catch #?(:clj Exception :cljs js/Error) ex 383 | {:type :fail :raw [str-e str-a] 384 | :expected-message (str "also attempted: (" str-e " " str-a ")") 385 | :actual-message (str " and got: " (p/get-message ex)) 386 | :result ["expected:" str-e 387 | "\n was:" (pr-str a)]}))) 388 | 389 | #?(:clj 390 | (defmethod compare-expr ::spec [e a str-e str-a] 391 | (try 392 | (let [valid? (resolve 'clojure.spec.alpha/valid?) 393 | explain-str (resolve 'clojure.spec.alpha/explain-str)] 394 | (if (valid? e a) 395 | {:type :pass} 396 | {:type :fail :raw [str-e str-a] 397 | :message (clojure.string/replace (explain-str e a) "\n" "\n ") 398 | :result [(pr-str a) "is not" str-e]})) 399 | (catch #?(:clj Exception :cljs js/Error) ex 400 | {:type :fail :raw [str-e str-a] 401 | :expected-message (str "also attempted: (" str-e " " str-a ")") 402 | :actual-message (str " and got: " (p/get-message ex)) 403 | :result ["expected:" str-e 404 | "\n was:" (pr-str a)]})))) 405 | 406 | (defn find-failures [the-seq] 407 | (seq (doall (remove (comp #{:pass} :type) the-seq)))) 408 | 409 | (defn find-successes [the-seq] 410 | (first (filter (comp #{:pass} :type) the-seq))) 411 | 412 | (defmethod compare-expr ::from-each [e {a ::from-each str-i-a ::from-each-body} str-e str-a] 413 | (if-let [failures (find-failures (for [{ts ::the-result rd ::ref-data} a] 414 | (assoc (compare-expr e ts str-e str-i-a) 415 | :ref-data rd)))] 416 | {:type :fail 417 | :raw [str-e str-a] 418 | :message (format "the list: %s" (pr-str (map (fn [x] (if-let [y (::in x)] y x)) 419 | (map ::the-result a)))) 420 | :list (mapv #(assoc % :show-raw true) failures)} 421 | {:type :pass})) 422 | 423 | (defmethod compare-expr ::more [{es ::more} a str-e str-a] 424 | (if-let [failures (find-failures (for [{:keys [e str-e a-fn gen-str-a]} es] 425 | (compare-expr 426 | e 427 | (try (a-fn a) (catch #?(:clj Throwable :cljs js/Error) t t)) 428 | str-e (gen-str-a str-a))))] 429 | {:type :fail 430 | :raw [str-e str-a] 431 | :message (format "actual val: %s" (pr-str a)) 432 | :list (mapv #(assoc % :show-raw true) failures)} 433 | {:type :pass})) 434 | 435 | (defmethod compare-expr ::in [e a str-e str-a] 436 | (cond 437 | (or (sequential? (::in a)) (set? (::in a))) 438 | (if (find-successes (for [a (::in a)] 439 | (compare-expr e a str-e str-a))) 440 | {:type :pass} 441 | {:type :fail 442 | :raw [str-e str-a] 443 | :list (map #(assoc % :show-raw true) (find-failures 444 | (for [a (::in a)] 445 | (compare-expr e a str-e a)))) 446 | :result [(if (::more e) str-e (format "val %s" (pr-str e))) "not found in" (::in a)]}) 447 | (and (map? (::in a)) (::more e)) 448 | {:type :fail :raw [str-e str-a] 449 | :message "Using both 'in with a map and 'more is not supported."} 450 | (map? (::in a)) 451 | (let [a (::in a)] 452 | (if (= e (select-keys a (keys e))) 453 | {:type :pass} 454 | {:type :fail 455 | :expected-message (format "in expected, not actual: %s" (first (clojure.data/diff e a))) 456 | :actual-message (format "in actual, not expected: %s" (first (clojure.data/diff a e))) 457 | :raw [str-e str-a] 458 | :result ["expected:" (pr-str e) "in" (pr-str a)]})) 459 | :default {:type :fail :raw [str-e str-a] 460 | :result ["You supplied:" (pr-str (::in a))] 461 | :message "You must supply a list, set, or map when using (in)"})) 462 | 463 | (defmethod compare-expr ::expect-instance [e a str-e str-a] 464 | (if (instance? e a) 465 | {:type :pass} 466 | {:type :fail :raw [str-e str-a] 467 | :expected-message (str "expected: " a " to be an instance of " e) 468 | :actual-message (str " was: " a " is an instance of " (type a))})) 469 | 470 | (defmethod compare-expr ::types [e a str-e str-a] 471 | (if (isa? a e) 472 | {:type :pass} 473 | {:type :fail :raw [str-e str-a] 474 | :expected-message (str "expected: " a " to be a " e)})) 475 | 476 | (defmethod compare-expr ::actual-exception [e a str-e str-a] 477 | (let [error {:type :error 478 | :raw [str-e str-a] 479 | :actual-message (str "exception in actual: " str-a) 480 | :result [a]}] 481 | (if (fn? e) 482 | (p/try 483 | (if (e a) 484 | {:type :pass} 485 | {:type :fail 486 | :raw [str-e str-a] 487 | :result ["exception thrown by" str-a "is not" str-e]}) 488 | (catch _ 489 | error)) 490 | error))) 491 | 492 | (defmethod compare-expr ::expected-exception [e a str-e str-a] 493 | {:type :error 494 | :raw [str-e str-a] 495 | :expected-message (str "exception in expected: " str-e) 496 | :result [e]}) 497 | 498 | (defmethod compare-expr ::regexps [e a str-e str-a] 499 | (compare-expr (str e) (str a) str-e str-a)) 500 | 501 | (defmethod compare-expr ::re-seq [e a str-e str-a] 502 | (if (and a (re-seq e a)) 503 | {:type :pass} 504 | {:type :fail, 505 | :raw [str-e str-a] 506 | :result ["regex" (pr-str e) "not found in" (pr-str a)]})) 507 | 508 | (defn strings-difference [e a] 509 | (let [matches (->> (map vector e a) (take-while (partial apply =)) (map first) (apply str)) 510 | e-diverges (clojure.string/replace e matches "") 511 | a-diverges (clojure.string/replace a matches "")] 512 | (str " matches: " (pr-str matches) 513 | "\n diverges: " (pr-str e-diverges) 514 | "\n &: " (pr-str a-diverges)))) 515 | 516 | (defmethod compare-expr ::strings [e a str-e str-a] 517 | {:type :fail :raw [str-e str-a] 518 | :result ["expected:" (pr-str e) 519 | "\n was:" (pr-str a)] 520 | :message (strings-difference e a)}) 521 | 522 | (defmethod compare-expr ::expect-exception [e a str-e str-a] 523 | (if (instance? e a) 524 | {:type :pass} 525 | {:type :fail :raw [str-e str-a] 526 | :result [str-a "did not throw" str-e]})) 527 | 528 | (defmethod compare-expr ::maps [e a str-e str-a] 529 | (let [[in-e in-a] (clojure.data/diff e a)] 530 | (if (and (nil? in-e) (nil? in-a)) 531 | {:type :pass} 532 | {:type :fail 533 | :expected-message (some->> in-e (format "in expected, not actual: %s")) 534 | :actual-message (some->> in-a (format "in actual, not expected: %s")) 535 | :raw [str-e str-a] 536 | :result ["expected:" (pr-str e) "\n was:" (pr-str a)]}))) 537 | 538 | (defmethod compare-expr ::sets [e a str-e str-a] 539 | {:type :fail 540 | :actual-message (format "in actual, not expected: %s" (first (clojure.data/diff a e))) 541 | :expected-message (format "in expected, not actual: %s" (first (clojure.data/diff e a))) 542 | :raw [str-e str-a] 543 | :result ["expected:" e "\n was:" (pr-str a)]}) 544 | 545 | (defmethod compare-expr ::sequentials [e a str-e str-a] 546 | (let [diff-fn (fn [e a] (seq (difference (set e) (set a))))] 547 | {:type :fail 548 | :actual-message (format "in actual, not expected: %s" (first (clojure.data/diff a e))) 549 | :expected-message (format "in expected, not actual: %s" (first (clojure.data/diff e a))) 550 | :raw [str-e str-a] 551 | :result ["expected:" e "\n was:" (pr-str a)] 552 | :message (cond 553 | (and 554 | (= (set e) (set a)) 555 | (= (count e) (count a)) 556 | (= (count e) (count (set a)))) 557 | "lists appear to contain the same items with different ordering" 558 | (and (= (set e) (set a)) (< (count e) (count a))) 559 | "some duplicate items in actual are not expected" 560 | (and (= (set e) (set a)) (> (count e) (count a))) 561 | "some duplicate items in expected are not actual" 562 | (< (count e) (count a)) 563 | "actual is larger than expected" 564 | (> (count e) (count a)) 565 | "expected is larger than actual")})) 566 | 567 | #?(:clj 568 | (defmacro doexpect [e a] 569 | `(let [e# (p/try ~e (catch t# t#)) 570 | a# (p/try ~a (catch t# t#))] 571 | (report 572 | (p/try (compare-expr e# a# '~e '~a) 573 | (catch e2# 574 | (compare-expr e2# a# '~e '~a))))))) 575 | 576 | #?(:clj 577 | (defn- hashname [[s & _ :as form]] 578 | (symbol (str (name s) (hash (str form)))))) 579 | 580 | #?(:clj 581 | (defmacro expect 582 | ([a] `(expect true (if ~a true false))) 583 | ([e a] 584 | `(def ~(vary-meta (hashname &form) assoc :expectation true) 585 | (fn [] (doexpect ~e ~a)))))) 586 | 587 | #?(:clj 588 | (defmacro expect-focused 589 | ([a] `(expect-focused true (if ~a true false))) 590 | ([e a] 591 | `(def ~(vary-meta (hashname &form) assoc :expectation true :focused true) 592 | (fn [] (doexpect ~e ~a)))))) 593 | 594 | #?(:clj 595 | (defmacro expanding [n] 596 | (p/expanding n))) 597 | 598 | #?(:clj 599 | (when-not (::hook-set (meta run-tests-on-shutdown)) 600 | (-> (Runtime/getRuntime) 601 | (.addShutdownHook 602 | (proxy [Thread] [] 603 | (run [] (when @run-tests-on-shutdown (run-all-tests)))))) 604 | (alter-meta! run-tests-on-shutdown assoc ::hook-set true))) 605 | 606 | #?(:clj 607 | (defn- var->symbol [v] 608 | (symbol (str (.ns v) "/" (.sym v))))) 609 | 610 | (defmulti localize type) 611 | #?(:cljs (defmethod localize cljs.core/Atom [a] (atom @a))) 612 | #?(:clj (defmethod localize Atom [a] (atom @a))) 613 | #?(:clj (defmethod localize Agent [a] (agent @a))) 614 | #?(:clj (defmethod localize Ref [a] (ref @a))) 615 | (defmethod localize :default [v] v) 616 | 617 | #?(:clj 618 | (defn- binding-&-localized-val [var] 619 | (when (p/bound? var) 620 | (when-let [vv @var] 621 | (when (p/iref-types (type vv)) 622 | (let [sym (var->symbol var)] 623 | [sym (list 'localize `(deref (var ~sym)))])))))) 624 | 625 | #?(:clj 626 | (defn- default-local-vals [namespaces] 627 | (->> 628 | namespaces 629 | (mapcat (comp vals ns-interns)) 630 | (mapcat binding-&-localized-val) 631 | (remove nil?) 632 | vec))) 633 | 634 | #?(:clj 635 | (defmacro redef-state [namespaces & forms] 636 | `(with-redefs ~(default-local-vals namespaces) ~@forms))) 637 | 638 | #?(:clj 639 | (defmacro freeze-time [time & forms] ;TODO impl for cljs 640 | `(try 641 | (DateTimeUtils/setCurrentMillisFixed (.getMillis ~time)) 642 | ~@forms 643 | (finally 644 | (DateTimeUtils/setCurrentMillisSystem))))) 645 | 646 | #?(:clj 647 | (defmacro ^{:private true} assert-args [fnname & pairs] 648 | `(do (when-not ~(first pairs) 649 | (throw (IllegalArgumentException. 650 | ~(str fnname " requires " (second pairs))))) 651 | ~(let [more (nnext pairs)] 652 | (when more 653 | (list* `assert-args fnname more)))))) 654 | 655 | #?(:clj 656 | (defmacro context [[sym-kw val & contexts :as args] & forms] 657 | (assert-args context 658 | (vector? args) "a vector for its contexts" 659 | (even? (count args)) "an even number of forms in the contexts vector") 660 | (if (empty? contexts) 661 | `(~(symbol (name sym-kw)) ~val 662 | ~@forms) 663 | `(~(symbol (name sym-kw)) ~val 664 | (context ~(vec contexts) 665 | ~@forms))))) 666 | 667 | #?(:clj 668 | (defmacro from-each [seq-exprs body-expr] 669 | (let [vs (for [[p1 p2 :as pairs] (partition 2 seq-exprs) 670 | :when (and (not= :when p1) (not= :while p1)) 671 | :let [vars (->> (if (= p1 :let) 672 | p2 673 | pairs) 674 | destructure 675 | (keep-indexed #(when (even? %1) %2)) 676 | (map str) 677 | distinct 678 | (remove (partial re-find #"^(map|vec)__\d+$")))] 679 | v vars] 680 | v)] 681 | `(hash-map ::from-each (doall (for ~seq-exprs 682 | {::the-result (p/try ~body-expr 683 | (catch t# t#)) 684 | ::ref-data ~(vec (interleave vs (map symbol vs)))})) 685 | ::from-each-body '~body-expr 686 | ::from-each-flag true)))) 687 | 688 | #?(:clj 689 | (defmacro more [& expects] 690 | `{::more [~@(map (fn [e] {:e e 691 | :str-e `'~e 692 | :gen-str-a `(fn [x#] x#) 693 | :a-fn `(fn [x#] x#)}) 694 | expects)]})) 695 | 696 | #?(:clj 697 | (defmacro more-> [& expect-pairs] 698 | (assert-args more-> 699 | (even? (count expect-pairs)) "an even number of forms.") 700 | `{::more [~@(map (fn [[e a-form]] 701 | {:e e 702 | :str-e `'~e 703 | :gen-str-a `(fn [x#] (->> (expanding (-> x# ~a-form)) 704 | (replace {'x# x#}))) 705 | :a-fn `(fn [x#] (-> x# ~a-form))}) 706 | (partition 2 expect-pairs))]})) 707 | 708 | #?(:clj 709 | (defmacro more-of [let-sexp & expect-pairs] 710 | (assert-args more-of 711 | (even? (count expect-pairs)) "an even number of expect-pairs") 712 | `{::more [~@(map (fn [[e a-form]] 713 | {:e e 714 | :str-e `'~e 715 | :gen-str-a `(fn [x#] (list '~'let ['~let-sexp x#] 716 | '~a-form)) 717 | :a-fn `(fn [~let-sexp] ~a-form)}) 718 | (partition 2 expect-pairs))]})) 719 | 720 | 721 | 722 | #?(:clj 723 | (defmacro side-effects [fn-vec & forms] 724 | (assert-args side-effects 725 | (vector? fn-vec) "a vector for its fn-vec") 726 | (let [side-effects-sym (gensym "conf-fn")] 727 | `(let [~side-effects-sym (atom [])] 728 | (with-redefs ~(vec (interleave fn-vec (repeat `(fn [& args#] (swap! ~side-effects-sym conj args#))))) 729 | ~@forms) 730 | @~side-effects-sym)))) 731 | 732 | (defn approximately 733 | "Given a value and an optional delta (default 0.001), return a predicate 734 | that expects its argument to be within that delta of the given value." 735 | ([^double v] (approximately v 0.001)) 736 | ([^double v ^double d] 737 | (fn [x] (<= (- v (Math/abs d)) x (+ v (Math/abs d)))))) 738 | 739 | (defrecord Functionally [e-fn a-fn differ] 740 | CustomPred 741 | (expect-fn [e a] (= (e-fn a) (a-fn a))) 742 | (expected-message [e a str-e str-a] (format "expected: %s" (e-fn a))) 743 | (actual-message [e a str-e str-a] (format " actual: %s" (a-fn a))) 744 | (message [e a str-e str-a] 745 | (if differ 746 | (differ (e-fn a) (a-fn a)) 747 | "not functionally equivalent"))) 748 | 749 | (defn functionally 750 | "Given a pair of functions, return a custom predicate that checks that they 751 | return the same result when applied to a value. May optionally accept a 752 | 'difference' function that should accept the result of each function and 753 | return a string explaininhg how they actually differ. 754 | For explaining strings, you could use expectations/strings-difference." 755 | ([expected-fn actual-fn] (->Functionally expected-fn actual-fn nil)) 756 | ([expected-fn actual-fn difference-fn] 757 | (->Functionally expected-fn actual-fn difference-fn))) 758 | -------------------------------------------------------------------------------- /src/cljc/expectations/platform.cljc: -------------------------------------------------------------------------------- 1 | (ns expectations.platform 2 | (:refer-clojure :exclude [bound? format ns-name]) 3 | (:require #?(:clj [clojure.pprint :as pprint]) 4 | #?(:cljs [cljs.analyzer]) 5 | #?(:cljs [goog.string]) 6 | #?(:cljs [goog.string.format])) 7 | #?(:cljs (:require-macros expectations.platform)) 8 | #?(:clj (:import (clojure.lang Agent Atom Ref)))) 9 | 10 | #?(:clj 11 | (defmacro cljs? [] 12 | (boolean (:ns &env)))) 13 | 14 | #?(:clj 15 | (defn expanding [n] 16 | (if (cljs?) 17 | `(cljs.analyzer/macroexpand-1 {} '~n) 18 | `(macroexpand-1 '~n)))) 19 | 20 | #?(:clj 21 | (defmacro try [& body] 22 | (let [[_ & catch-body] (last body)] 23 | `(try ~@(butlast body) 24 | (catch ~(if (:ns &env) `js/Error `Throwable) ~@catch-body))))) 25 | 26 | (defn ns-name [ns] 27 | #?(:clj (if (symbol? ns) ns (clojure.core/ns-name ns)) 28 | :cljs (if (symbol? ns) ns))) 29 | 30 | (def bound? 31 | #?(:clj clojure.core/bound? 32 | :cljs (fn [& vars] (every? #(deref %) vars)))) 33 | 34 | (def format 35 | #?(:clj clojure.core/format 36 | :cljs goog.string/format)) 37 | 38 | (defn nodejs? [] 39 | #?(:clj false 40 | :cljs (= (js* "typeof(process)") "object"))) 41 | 42 | (defn getenv [var] 43 | #?(:clj (System/getenv var) 44 | :cljs (aget (if (nodejs?) js/process.env js/window) var))) 45 | 46 | (defn get-message [e] (-> e 47 | #?(:clj .getMessage 48 | :cljs .-message))) 49 | 50 | (defn nano-time [] 51 | #?(:clj (System/nanoTime) 52 | :cljs (if (nodejs?) 53 | (-> js/process .hrtime js->clj 54 | (#(+ (* 1e9 (% 0)) (% 1)))) 55 | (js/performance.now)))) 56 | 57 | 58 | (defn on-windows? [] 59 | (re-find #"[Ww]in" 60 | #?(:clj (System/getProperty "os.name") 61 | :cljs (if (nodejs?) (.-platform js/process) "")))) 62 | 63 | (def pprint 64 | #?(:clj pprint/pprint 65 | :cljs println)) ;until there's a usable cljs pprint port 66 | 67 | (defn print-stack-trace [e] 68 | (-> e 69 | #?(:clj .printStackTrace 70 | :cljs .-stack) println)) 71 | 72 | (def iref-types 73 | #?(:clj #{Agent Atom Ref} 74 | :cljs #{cljs.core/Atom})) 75 | -------------------------------------------------------------------------------- /src/cljs/expectations/cljs.clj: -------------------------------------------------------------------------------- 1 | (ns expectations.cljs 2 | (:require [cljs.analyzer.api :as aapi] 3 | [expectations :as e])) 4 | 5 | (defn ->cljs-vars [namespace] 6 | (assert (symbol? namespace) (str namespace)) 7 | (assert (aapi/find-ns namespace) (str namespace)) 8 | (->> (aapi/ns-interns namespace) 9 | (filter (fn [[_ v]] (not (:macro v)))) 10 | (map (fn [[k _]] `(var ~(symbol (name namespace) (name k))))) 11 | (into []))) 12 | 13 | (defmacro run-tests [& namespaces] 14 | (assert (every? symbol? namespaces) (str namespaces)) 15 | (assert (every? aapi/find-ns namespaces) (str "Some namespaces were not properly require'd: " namespaces)) 16 | `(let [all-vars# [~@(mapcat ->cljs-vars namespaces)] 17 | all-vars# (sort-by (fn [v#] [(-> v# meta :ns) (-> v# meta :line)]) all-vars#) 18 | vars-by-kind# (e/by-kind all-vars#) 19 | expectations# (:expectation vars-by-kind#)] 20 | (if-let [focused# (:focused vars-by-kind#)] 21 | (doto (assoc (e/test-vars (assoc vars-by-kind# :expectation focused#) (- (count expectations#) (count focused#))) 22 | :type :summary) 23 | (e/report)) 24 | (doto (assoc (e/test-vars vars-by-kind# 0) 25 | :type :summary) 26 | (e/report))))) 27 | 28 | (defmacro run-all-tests 29 | ([] `(run-all-tests nil)) 30 | ([re] `(run-tests 31 | ~@(cond->> (->> (aapi/all-ns) 32 | (filter (comp not #(re-matches #"cljs\..+|clojure\..+|expectations(?:\.platform)?" %) name)) 33 | (filter aapi/find-ns)) 34 | re (filter #(re-matches re (name %))))))) 35 | -------------------------------------------------------------------------------- /src/clojure/expectations/junit/runner.clj: -------------------------------------------------------------------------------- 1 | (ns expectations.junit.runner 2 | (:require expectations) 3 | (:import [java.io File] 4 | [org.junit.runner Runner Description] 5 | [org.junit.runner.notification Failure] 6 | [junit.framework AssertionFailedError ComparisonFailure] 7 | [java.lang.annotation Annotation])) 8 | 9 | (def empty-ann-arr (make-array Annotation 0)) 10 | 11 | (defn format-test-name [test-name test-meta] 12 | (str test-name " (" (:file test-meta) ":" (:line test-meta) " " (:name test-meta) ")")) 13 | 14 | (defn start [notifier descs test-name test-meta] 15 | (.fireTestStarted notifier (descs (format-test-name test-name test-meta)))) 16 | 17 | (defn finish [notifier descs test-name test-meta] 18 | (.fireTestFinished notifier (descs (format-test-name test-name test-meta)))) 19 | 20 | (defn failure [notifier descs test-name test-meta info] 21 | (.fireTestFailure notifier 22 | (expectations.junit.ExpectationsFailure. (descs (format-test-name test-name test-meta)) (str "failure in (" (expectations/test-file test-meta) ") : " (:ns test-meta) "\n" info "\n")))) 23 | 24 | (defn create-desc [accum v] 25 | (let [test-name (format-test-name (expectations/test-name (meta v)) (meta v))] 26 | (assoc accum test-name (Description/createSuiteDescription test-name empty-ann-arr)))) 27 | 28 | (defn ignored-fns [{:keys [className fileName]}] 29 | (or (= fileName "expectations.clj") 30 | (= fileName "expectations_options.clj") 31 | (re-seq #"clojure.lang" className) 32 | (re-seq #"clojure.core" className) 33 | (re-seq #"clojure.main" className) 34 | (re-seq #"expectations.junit.runner" className) 35 | (re-seq #"expectations.junit.ExpectationsTestRunner" className) 36 | (re-seq #"com.intellij" className) 37 | (re-seq #"org.junit.runner.JUnitCore" className) 38 | (re-seq #"org.junit.runners" className) 39 | (re-seq #"sun.reflect" className) 40 | (re-seq #"java.lang" className))) 41 | 42 | (defn clj-file? [file-name] 43 | (re-seq #".clj$" (:absolutePath file-name))) 44 | 45 | (defn in-loaded-file? [file-names v] (file-names (:file (meta v)))) 46 | 47 | (defn create-runner [source] 48 | (let [files (->> source .testPath File. file-seq (map bean) (remove :hidden ) (remove :directory ) (filter clj-file?)) 49 | _ (doseq [{:keys [absolutePath]} files] (load-file absolutePath)) 50 | file-names (set (map :absolutePath files)) 51 | suite-description (Description/createSuiteDescription (-> source class .getName) empty-ann-arr) 52 | vars-by-kind (->> (all-ns) (mapcat expectations/->vars) (expectations/by-kind)) 53 | filtered-vars (->> (:expectation vars-by-kind) (filter (partial in-loaded-file? file-names))) 54 | descs (reduce create-desc {} filtered-vars)] 55 | (doseq [desc (vals descs)] (.addChild suite-description desc)) 56 | (proxy [Runner] [] 57 | (getDescription [] suite-description) 58 | (run [notifier] 59 | (expectations/disable-run-on-shutdown) 60 | (let [results (binding [expectations/started (partial start notifier descs) 61 | expectations/finished (partial finish notifier descs) 62 | expectations/fail (partial failure notifier descs) 63 | expectations/ignored-fns ignored-fns 64 | expectations/summary (fn [_])] 65 | (expectations/run-tests-in-vars (assoc vars-by-kind :expectation filtered-vars)))] 66 | (.fireTestStarted notifier suite-description) 67 | (when (or (< 0 (:error results)) (< 0 (:fail results))) 68 | (.fireTestFailure notifier (expectations.junit.ExpectationsFailure. suite-description ""))) 69 | (.fireTestFinished notifier suite-description)))))) 70 | -------------------------------------------------------------------------------- /src/java/expectations/junit/ExpectationsFailure.java: -------------------------------------------------------------------------------- 1 | package expectations.junit; 2 | 3 | import org.junit.runner.Description; 4 | import org.junit.runner.notification.Failure; 5 | 6 | import java.io.PrintWriter; 7 | 8 | public class ExpectationsFailure extends Failure { 9 | 10 | public ExpectationsFailure(Description description, String message) { 11 | super(description, new NullException(message)); 12 | } 13 | 14 | private static class NullException extends Throwable { 15 | private final String message; 16 | 17 | public NullException(String message) { 18 | this.message = message; 19 | } 20 | 21 | @Override 22 | public String getMessage() { 23 | return message; 24 | } 25 | 26 | @Override 27 | public void printStackTrace(PrintWriter writer) { 28 | writer.println(message); 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return message; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/java/expectations/junit/ExpectationsTestRunner.java: -------------------------------------------------------------------------------- 1 | package expectations.junit; 2 | 3 | import clojure.lang.RT; 4 | import org.junit.runner.Description; 5 | import org.junit.runner.Runner; 6 | import org.junit.runner.notification.RunNotifier; 7 | 8 | public class ExpectationsTestRunner extends Runner { 9 | private final Runner clojureRunner; 10 | 11 | public ExpectationsTestRunner(Class testSourceClass) throws Exception { 12 | TestSource source = testSourceClass.newInstance(); 13 | RT.loadResourceScript("expectations/junit/runner.clj"); 14 | clojureRunner = (Runner) RT.var("expectations.junit.runner", "create-runner").invoke(source); 15 | } 16 | 17 | @Override 18 | public Description getDescription() { 19 | return clojureRunner.getDescription(); 20 | } 21 | 22 | @Override 23 | public void run(RunNotifier notifier) { 24 | clojureRunner.run(notifier); 25 | } 26 | 27 | public interface TestSource { 28 | String testPath(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/cljc/expectations/test.cljc: -------------------------------------------------------------------------------- 1 | (ns expectations.test 2 | #?(:cljs (:require-macros [expectations.cljs :as ecljs])) 3 | (:require [success.expectations-options] 4 | [success.success-examples] 5 | [success.nested.success-examples])) 6 | 7 | #?(:cljs 8 | (defn -main [] 9 | (ecljs/run-all-tests))) 10 | 11 | #?(:cljs (enable-console-print!)) 12 | #?(:cljs (set! *main-cli-fn* -main)) 13 | -------------------------------------------------------------------------------- /test/cljc/success/expectations_options.cljc: -------------------------------------------------------------------------------- 1 | (ns success.expectations-options 2 | (:require [expectations] 3 | [success.success-examples-src])) 4 | 5 | (defn turn-iref-warnings-on 6 | "turn iref modification warnings on" 7 | {:expectations-options :before-run} 8 | [] 9 | (expectations/warn-on-iref-updates)) 10 | 11 | (defn am-i-done? 12 | "turn iref modification warnings on" 13 | {:expectations-options :after-run} 14 | [] 15 | (println "yeah, you're done.")) 16 | 17 | (defn in-context 18 | "rebind a var to verify that the expecations are run in the defined context" 19 | {:expectations-options :in-context} 20 | [work] 21 | (with-redefs [success.success-examples-src/a-fn-to-be-rebound (constantly :a-rebound-val)] 22 | (work))) 23 | -------------------------------------------------------------------------------- /test/cljc/success/nested/success_examples.cljc: -------------------------------------------------------------------------------- 1 | (ns success.nested.success-examples 2 | #?(:cljs (:require-macros [expectations :refer [expect]])) 3 | (:require #?(:clj [expectations :refer :all] 4 | :cljs [expectations :refer [in]]))) 5 | 6 | ;; number equality 7 | (expect 1 (do 1)) 8 | 9 | ;; string equality 10 | (expect "foo" (identity "foo")) 11 | 12 | ; map equality 13 | (expect {:foo 1 :bar 2 :car 4} (assoc {} :foo 1 :bar 2 :car 4)) 14 | 15 | ;; is the regex in the string 16 | (expect #"foo" (str "boo" "foo" "ar")) 17 | 18 | ;; does the form throw an expeted exception 19 | #?(:clj 20 | (expect ArithmeticException (/ 12 0))) 21 | 22 | ;; verify the type of the result 23 | (expect string? "foo") 24 | #?(:clj 25 | (expect String "foo")) 26 | #?(:cljs 27 | (expect js/String "foo")) 28 | 29 | ;; k/v pair in map. matches subset 30 | (expect {:foo 1} (in {:foo 1 :cat 4})) 31 | 32 | ;; key in set 33 | (expect :foo (in (conj #{:foo :bar} :cat))) 34 | 35 | ;; val in list 36 | (expect :foo (in (conj [:bar] :foo))) 37 | 38 | ;; expect boolean 39 | (expect empty? (list)) 40 | -------------------------------------------------------------------------------- /test/cljc/success/success_examples.cljc: -------------------------------------------------------------------------------- 1 | (ns success.success-examples 2 | (:require #?(:clj [expectations :refer :all] 3 | :cljs [expectations 4 | :refer [approximately functionally in localize no-op 5 | strings-difference] 6 | :refer-macros [expanding 7 | expect 8 | expect-focused 9 | from-each 10 | more 11 | more-> 12 | more-of 13 | redef-state 14 | side-effects]]) 15 | #?(:clj [success.success-examples-src :refer [a-macro cljs?]] 16 | :cljs [success.success-examples-src :refer-macros [a-macro cljs?]])) 17 | #?(:clj (:import (java.util AbstractMap ArrayList HashMap) 18 | (org.joda.time DateTime)))) 19 | 20 | #?(:cljs 21 | (defn spit [f content & options] (.log js/console content))) 22 | 23 | ;; expect to be on the right platform 24 | (expect 25 | #?(:clj (not (cljs?)) 26 | :cljs (cljs?))) 27 | 28 | ;; expect a truthy value 29 | (expect true) 30 | (expect "x") 31 | (expect (not false)) 32 | 33 | ;; number equality 34 | (expect 1 (do 1)) 35 | 36 | ;; string equality 37 | (expect "foo" (identity "foo")) 38 | 39 | ;; map equality 40 | (expect {:foo 1 :bar 2 :car 4} (assoc {} :bar 2 :foo 1 :car 4)) 41 | (expect {:foo 1 :bar 2 :car 4} {:bar 2 :foo 1 :car 4}) 42 | (expect {:foo 1 :bar 2 :car 4} (array-map :bar 2 :foo 1 :car 4)) 43 | (expect {:foo 1 :bar 2 :car 4} (sorted-map :bar 2 :foo 1 :car 4)) 44 | (expect {:foo 1 :bar 2 :car 4} (hash-map :bar 2 :foo 1 :car 4)) 45 | (expect {:foo (int 1)} {:foo (long 1)}) 46 | 47 | ;; record equality 48 | (defrecord Foo [a b c]) 49 | 50 | (expect (->Foo :a :b :c) (->Foo :a :b :c)) 51 | (expect (->Foo "a" "b" "c") (->Foo "a" "b" "c")) 52 | 53 | (expect (success.success-examples-src/->ARecord "data") 54 | (success.success-examples-src/->ARecord "data")) 55 | 56 | ;; is the regex in the string 57 | (expect #"foo" (str "boo" "foo" "ar")) 58 | 59 | ;; does the form throw an expeted exception 60 | #?(:clj (expect ArithmeticException (/ 12 0))) 61 | 62 | ;; can we process an exception with predicates? 63 | #?(:clj (expect (more ArithmeticException 64 | (comp (partial re-find #"Divide by zero") 65 | (memfn getMessage))) 66 | (/ 12 0))) 67 | #?(:clj (expect (more-of ex 68 | ArithmeticException ex 69 | "Divide by zero" (.getMessage ex)) 70 | (/ 12 0))) 71 | 72 | ;; verify the type of the result 73 | #?(:clj (expect String "foo") 74 | :cljs (expect js/String "foo")) 75 | (expect string? "foo") 76 | 77 | ;; k/v pair in map. matches subset 78 | (expect {:foo 1} (in {:foo 1 :cat 4})) 79 | 80 | ;; k/v pair in record. matches subset 81 | (expect {:a :a} (in (->Foo :a :b :c))) 82 | 83 | ;; key in set 84 | (expect :foo (in (conj #{:foo :bar} :cat))) 85 | 86 | ;; val in list 87 | (expect :foo (in (conj [:bar] :foo))) 88 | 89 | ;; expect truthy fn return 90 | (expect empty? (list)) 91 | 92 | ;; sorted map equality 93 | (expect (sorted-map-by > 1 :a 2 :b) (sorted-map-by > 1 :a 2 :b)) 94 | 95 | (expect '(clojure.core/println 1 2 (println 100) 3) 96 | (expanding (success.success-examples-src/a-macro 1 2 (println 100) 3))) 97 | 98 | (expect (more vector? not-empty) [1 2 3]) 99 | 100 | #?(:clj 101 | (expect (more-> 0 .size 102 | true .isEmpty) 103 | (ArrayList.))) 104 | 105 | (expect (more-> 2 (-> first (+ 1)) 106 | 3 last) 107 | [1 2 3]) 108 | 109 | (expect (more-> 2 :a 110 | 4 :b) 111 | {:a 2 :b 4}) 112 | 113 | (expect (more-of x 114 | vector? x 115 | 1 (first x)) 116 | [1 2 3]) 117 | 118 | (expect ["/tmp/hello-world" "some data" :append true] 119 | (second (side-effects [spit] 120 | (spit "some other stuff" "xy") 121 | (spit "/tmp/hello-world" "some data" :append true)))) 122 | 123 | (expect empty? 124 | (side-effects [spit] "spit never called")) 125 | 126 | (expect [["/tmp/hello-world" "some data" :append true] 127 | ["/tmp/hello-world" "some data" :append true]] 128 | (side-effects [spit] 129 | (spit "/tmp/hello-world" "some data" :append true) 130 | (spit "/tmp/hello-world" "some data" :append true))) 131 | 132 | (expect ["/tmp/hello-world" "some data" :append true] 133 | (in (side-effects [spit] 134 | (spit "some other stuff" "xy") 135 | (spit "/tmp/hello-world" "some data" :append true)))) 136 | 137 | (expect (more-of [path data action {:keys [a c]}] 138 | string? path 139 | #"some da" data 140 | keyword? action 141 | :b a 142 | :d c) 143 | (in (side-effects [spit] 144 | (spit "/tmp/hello-world" "some data" :append {:a :b :c :d :e :f})))) 145 | 146 | (expect (more-of [a b] number? a) 147 | (from-each [x [[1 2] [1 3]]] 148 | x)) 149 | 150 | (expect 1 151 | (from-each [x [[1 2] [1 3]]] 152 | (in x))) 153 | 154 | (expect (more-of a number? a) 155 | (from-each [x [[1 2] [1 3]]] 156 | (in x))) 157 | 158 | (expect {1 2} 159 | (from-each [x [{1 2} {1 2 3 4}]] 160 | (in x))) 161 | 162 | (expect (more identity not-empty) 163 | (in (side-effects [spit] 164 | (spit "/tmp/hello-world" "some data" :append {:a :b :c :d :e :f})))) 165 | 166 | (expect (more-> 167 | string? (nth 0) 168 | #"some da" (nth 1) 169 | keyword? (nth 2) 170 | {:a :b :c :d} (-> (nth 3) (select-keys [:a :c]))) 171 | (in (side-effects [spit] 172 | (spit "/tmp/hello-world" "some data" :append {:a :b :c :d :e :f})))) 173 | 174 | (expect not-empty 175 | (side-effects [spit] 176 | (spit "/tmp/hello-world" "some data" :append {:a :b :c :d :e :f}))) 177 | 178 | ;; redef state within the context of a test 179 | (expect :atom 180 | (do 181 | (reset! success.success-examples-src/an-atom "atom") 182 | (redef-state [success.success-examples-src] 183 | (reset! success.success-examples-src/an-atom :atom) 184 | @success.success-examples-src/an-atom))) 185 | 186 | (expect :private-atom 187 | (do 188 | (reset! @#'success.success-examples-src/an-private-atom "private-atom") 189 | (redef-state [success.success-examples-src] 190 | (reset! @#'success.success-examples-src/an-private-atom :private-atom) 191 | @@#'success.success-examples-src/an-private-atom))) 192 | 193 | (expect "atom" 194 | (do 195 | (reset! success.success-examples-src/an-atom "atom") 196 | (redef-state [success.success-examples-src] 197 | (reset! success.success-examples-src/an-atom :atom)) 198 | @success.success-examples-src/an-atom)) 199 | 200 | (expect "private-atom" 201 | (do 202 | (reset! @#'success.success-examples-src/an-private-atom "private-atom") 203 | (redef-state [success.success-examples-src] 204 | (reset! @#'success.success-examples-src/an-private-atom :private-atom)) 205 | @@#'success.success-examples-src/an-private-atom)) 206 | 207 | ;; use freeze-time to set the current time while a test is running 208 | #?(:clj ;TODO the same in cljs 209 | (let [now (DateTime.)] 210 | (freeze-time now (DateTime.)) 211 | (freeze-time now (DateTime.)))) 212 | 213 | ;; freeze-time only affects wrapped forms 214 | #?(:clj ;TODO the same in cljs 215 | (expect (not= (DateTime. 1) 216 | (do 217 | (freeze-time (DateTime. 1)) 218 | (DateTime.))))) 219 | ;; freeze-time resets the frozen time even when an exception occurs 220 | #?(:clj ;TODO the same in cljs 221 | (expect (not= (DateTime. 1) 222 | (do 223 | (try 224 | (freeze-time (DateTime. 1) 225 | (throw (RuntimeException. "test finally"))) 226 | (catch Exception e)) 227 | (DateTime.))))) 228 | 229 | ;; use context to limit the number of indentions while using redef-state, with-redefs or freeze-time 230 | #?(:clj ;TODO the same in cljs 231 | (let [now (DateTime.)] 232 | [now now] 233 | (context [:redef-state [success.success-examples-src] 234 | :with-redefs [spit no-op] 235 | :freeze-time now] 236 | (spit now) 237 | (vector now (DateTime.))))) 238 | 239 | ;; ensure equality matching where possible 240 | (expect no-op no-op) 241 | #?(:clj 242 | (expect AbstractMap HashMap)) 243 | (expect #"a" #"a") 244 | #?(:clj (expect RuntimeException RuntimeException) 245 | :cljs (expect js/Error js/Error)) 246 | 247 | (expect :a-rebound-val (success.success-examples-src/a-fn-to-be-rebound)) 248 | 249 | (expect string? 250 | (from-each [letter ["a" "b" "c"]] letter)) 251 | 252 | (expect even? (from-each [num [1 2 3] 253 | :let [numinc1 (inc num) 254 | numinc2 (inc num)]] 255 | (* 10 numinc2))) 256 | 257 | (expect (success.success-examples-src/->ConstantlyTrue) [1 2 3 4]) 258 | 259 | (expect (more-> false identity 260 | #?(:clj AssertionError :cljs js/Error) assert) 261 | false) 262 | 263 | (expect #?(:clj AssertionError :cljs js/Error) (from-each [a [1 2]] (assert (string? a)))) 264 | 265 | (expect (approximately 0.33 0.1) (/ 1 3)) 266 | 267 | (expect (approximately 1000 10) 999) 268 | 269 | (expect (approximately 1000 10) 1009) 270 | 271 | (expect (approximately 0.333) 0.333) 272 | 273 | (expect (functionally str name strings-difference) 274 | (from-each [s ['a "b"]] s)) 275 | -------------------------------------------------------------------------------- /test/cljc/success/success_examples_src.cljc: -------------------------------------------------------------------------------- 1 | (ns success.success-examples-src 2 | (:refer-clojure :exclude [format]) 3 | (:require [expectations :refer [CustomPred]] 4 | [expectations.platform :as p :refer [format]])) 5 | 6 | (def an-atom (atom "atom")) 7 | 8 | (def ^:private an-private-atom (atom "private-atom")) 9 | 10 | (defn a-fn-to-be-rebound []) 11 | 12 | (defrecord ConstantlyTrue [] 13 | CustomPred 14 | (expect-fn [e a] true) 15 | (expected-message [e a str-e str-a] (format "expected %s" e)) 16 | (actual-message [e a str-e str-a] (format "actual %s" a)) 17 | (message [e a str-e str-a] (format "%s & %s" str-e str-a))) 18 | 19 | ;; macro expansion 20 | #?(:clj 21 | (defmacro a-macro [& args] 22 | `(println ~@args))) 23 | 24 | (defrecord ARecord [data]) 25 | 26 | #?(:clj 27 | (defmacro cljs? [] 28 | `(p/cljs?))) 29 | -------------------------------------------------------------------------------- /test/clojure/failure/failure_examples.clj: -------------------------------------------------------------------------------- 1 | (ns failure.failure-examples 2 | (:use expectations) 3 | (:require success.success-examples-src)) 4 | 5 | (defn two [] (/ 12 0)) 6 | (defn one [] (two)) 7 | (defrecord Foo [a b c]) 8 | (defmacro a-macro [& args] 9 | `(println ~@args)) 10 | 11 | ;; errors 12 | (expect 1 (one)) 13 | 14 | (expect (one) 1) 15 | 16 | ;; number equality 17 | (expect 1 (identity 2)) 18 | 19 | ;; string equality 20 | (expect "foos" (identity "foode")) 21 | 22 | ;; map equality 23 | (expect {:foo 1 :bar 3 :dog 3 :car 4} (assoc {} :foo 1 :bar "3" :cat 4)) 24 | 25 | ;; record equality 26 | (expect (->Foo :a :b :c) (->Foo :c :b :a)) 27 | 28 | ;; list equality 29 | (expect [1 2 3 2 4] [3 2 1 3]) ;; expected larger msg 30 | (expect [3 2 1 3] [1 2 3 2 4]) ;; actual larger msg 31 | (expect [1 2 3 5] [5 2 1 3]) ;; wrong ordering msg 32 | (expect [2 2 1 3] [2 1 3]) ;; dupes in expected, not actual msg 33 | (expect [2 1 3] [2 2 1 3]) ;; dupes in actual, not expected msg 34 | (expect [99 1 2] [100 1 3]) ;; show diff results 35 | 36 | ;; set equality 37 | (expect #{:foo :bar :dog :car } (conj #{} :foo :bar :cat )) 38 | 39 | ;; lazy cons printing 40 | (expect [1 2] (map - [1 2])) 41 | 42 | ;; is the regex in the string 43 | (expect #"foo" (str "boo" "fo" "ar")) 44 | 45 | ;; does the form throw an expeted exception 46 | (expect ArithmeticException (/ 12 12)) 47 | 48 | ;; verify the type of the result 49 | (expect String 1) 50 | 51 | ;; k/v pair in map. matches subset 52 | (expect {:foos 1 :cat 5} (in {:foo 1 :cat 4})) 53 | 54 | ;; k/v pair in record. matches subset 55 | (expect {:a :a :b :b} (in (->Foo :c :b :a))) 56 | 57 | ;; key in set 58 | (expect "foos" (in (conj #{:foo :bar } "cat"))) 59 | 60 | ;; val in list 61 | (expect "foo" (in (conj ["bar"] :foo ))) 62 | 63 | ;; val in nil 64 | (expect "foo" (in nil)) 65 | 66 | ;; expect boolean 67 | (expect empty? (list 1)) 68 | 69 | ;; macro expansion 70 | (expect '(clojure.core/println 1 2 (println 100) 3) 71 | (expanding (a-macro 1 2 (println 101) 3))) 72 | 73 | ;; nested issues 74 | (expect {nil {nil nil} :z 1 :a 9 :b {:c Double/NaN :d 1 :e 2 :f {:g 10 :i 22}}} 75 | {:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}}) 76 | 77 | (expect "the cat jumped over the moon" "the cat jumped under the moon") 78 | 79 | (expect :original 80 | (with-redefs [success.success-examples-src/an-atom (atom :original)] 81 | (redef-state [success.success-examples-src] 82 | (reset! success.success-examples-src/an-atom :something-else) 83 | @success.success-examples-src/an-atom))) 84 | 85 | (expect :original 86 | (with-redefs [success.success-examples-src/an-private-atom (atom :original)] 87 | (redef-state [success.success-examples-src] 88 | (reset! @#'success.success-examples-src/an-private-atom :something-else) 89 | @@#'success.success-examples-src/an-private-atom))) 90 | 91 | (expect :atom 92 | (with-redefs [success.success-examples-src/an-atom (atom :original)] 93 | (do 94 | (redef-state [success.success-examples-src] 95 | (reset! success.success-examples-src/an-atom :atom)) 96 | @success.success-examples-src/an-atom))) 97 | 98 | (expect :atom 99 | (with-redefs [success.success-examples-src/an-private-atom (atom :original)] 100 | (do 101 | (redef-state [success.success-examples-src] 102 | (reset! @#'success.success-examples-src/an-private-atom :atom)) 103 | @@#'success.success-examples-src/an-private-atom))) 104 | 105 | (expect nil) 106 | (expect false) 107 | 108 | (expect even? (from-each [i [1 2 3]] 109 | i)) 110 | 111 | (expect even? (from-each [i [1 2 3] 112 | :let [ii (inc i) 113 | iii (inc ii)] 114 | :let [iiii (inc iii) 115 | iiiii (inc iiii)]] 116 | (dec iiiii))) 117 | 118 | (expect even? (from-each [[i {:keys [v1] {:strs [v3] :syms [v4] :or {v4 9}} :v2 :as all-of-vs} :as z] 119 | [[1 {:v1 3 :v2 {"v3" 5 'v4 7}}] 120 | [3 {:v1 4 :v2 {"v3" 6}}]] 121 | :let [ii (map inc [i v1 v3]) 122 | iii (map inc ii)] 123 | j iii 124 | :let [jj (inc j) 125 | jjj (inc jj)]] 126 | (dec jjj))) 127 | 128 | (defrecord ConstantlyFalse [] 129 | CustomPred 130 | (expect-fn [e a] false) 131 | (expected-message [e a str-e str-a] (format "expected %s" e)) 132 | (actual-message [e a str-e str-a] (format "actual %s" a)) 133 | (message [e a str-e str-a] (format "%s & %s" str-e str-a))) 134 | 135 | (expect (->ConstantlyFalse) [1 2 3 4]) 136 | 137 | (expect 4 138 | (from-each [x [[1 2] [1 3]]] 139 | (in x))) 140 | 141 | (expect (more-of x list? x 142 | vector? x) 143 | (in (side-effects [spit] 144 | (spit "/tmp/hello-world" "some data" :append {:a :b :c :d :e :f}) 145 | (spit "/tmp/hello-world" "some data" :append 1)))) 146 | 147 | (expect (more-of x 148 | list? x 149 | 2 (first x)) 150 | (conj [] 1 2 3)) 151 | 152 | (expect (more-of [x y z :as full-list] 153 | list? full-list 154 | 2 x 155 | 3 y 156 | 4 z) 157 | (conj [] 1 2 3)) 158 | 159 | (expect (more list? empty?) [1 2 3]) 160 | 161 | (expect (more-> 1 (-> identity .size) 162 | false .isEmpty) 163 | (java.util.ArrayList.)) 164 | 165 | (expect (more-> 1 (-> first (+ 1)) 166 | 4 last) 167 | (conj [] 1 2 3)) 168 | 169 | (expect "need to see exceptions here" (from-each [x [1 2 3]] 170 | (/ x 0))) 171 | 172 | (expect AssertionError (from-each [a ["2" 1]] (assert (string? a)))) 173 | -------------------------------------------------------------------------------- /test/clojure/failure/not.clj.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-expectations/expectations/4bd93577691420ba936372d90be8b201ef89f8a6/test/clojure/failure/not.clj.txt -------------------------------------------------------------------------------- /test/expectations/expectations_expectations.clj: -------------------------------------------------------------------------------- 1 | (ns expectations.expectations-expectations 2 | (:use expectations)) 3 | 4 | (expect " result 5 | 6 | expected-message 7 | actual-message 8 | message" 9 | (with-redefs [show-raw-choice (constantly false)] 10 | (->failure-message 11 | {:raw "raw" 12 | :result ["result"] 13 | :expected-message "expected-message" 14 | :actual-message "actual-message" 15 | :message "message"}))) 16 | 17 | (expect "(expect raw-e raw-a)\n 18 | result 19 | 20 | expected-message 21 | actual-message 22 | message" 23 | (with-redefs [show-raw-choice (constantly true)] 24 | (->failure-message 25 | {:raw '(raw-e raw-a) 26 | :result ["result"] 27 | :expected-message "expected-message" 28 | :actual-message "actual-message" 29 | :message "message"}))) 30 | 31 | (expect "(expect (whole thing) nil)\n 32 | the list blah 33 | 34 | (expect raw-e raw-a)\n 35 | result 36 | 37 | expected-message 38 | actual-message 39 | message 40 | 41 | (expect raw-e2 raw-a2)\n 42 | result2 43 | 44 | expected-message2 45 | actual-message2 46 | message2" 47 | 48 | (with-redefs [show-raw-choice (constantly true)] 49 | (->failure-message 50 | {:raw ['(whole thing)] 51 | :message "the list blah" 52 | :list [{:raw '(raw-e raw-a) 53 | :result ["result"] 54 | :expected-message "expected-message" 55 | :actual-message "actual-message" 56 | :message "message"} 57 | {:raw '(raw-e2 raw-a2) 58 | :result ["result2"] 59 | :expected-message "expected-message2" 60 | :actual-message "actual-message2" 61 | :message "message2"}]}))) 62 | 63 | (expect " result 64 | \n expected-message\n actual-message\n message" 65 | (with-redefs [show-raw-choice (constantly false)] 66 | (->failure-message 67 | {:raw '(raw-e raw-a) 68 | :result ["result"] 69 | :expected-message "expected-message" 70 | :actual-message "actual-message" 71 | :message "message"}))) 72 | 73 | (expect "" 74 | (with-redefs [show-raw-choice (constantly true)] 75 | (->failure-message {}))) 76 | 77 | (expect "" 78 | (with-redefs [show-raw-choice (constantly false)] 79 | (->failure-message {}))) 80 | -------------------------------------------------------------------------------- /test/java/FailureTest.java: -------------------------------------------------------------------------------- 1 | import expectations.junit.ExpectationsTestRunner; 2 | import org.junit.runner.RunWith; 3 | 4 | @RunWith(expectations.junit.ExpectationsTestRunner.class) 5 | public class FailureTest implements ExpectationsTestRunner.TestSource{ 6 | 7 | public String testPath() { 8 | return "test/clojure/failure"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/java/SuccessTest.java: -------------------------------------------------------------------------------- 1 | import expectations.junit.ExpectationsTestRunner; 2 | import org.junit.runner.RunWith; 3 | 4 | @RunWith(expectations.junit.ExpectationsTestRunner.class) 5 | public class SuccessTest implements ExpectationsTestRunner.TestSource{ 6 | 7 | public String testPath() { 8 | return "test/clojure/success"; 9 | } 10 | } 11 | --------------------------------------------------------------------------------