├── .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 "[36m(expect raw-e raw-a)\n[0m
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 "[36m(expect (whole thing) nil)\n[0m
32 | the list blah
33 |
34 | [36m(expect raw-e raw-a)\n[0m
35 | result
36 |
37 | expected-message
38 | actual-message
39 | message
40 |
41 | [36m(expect raw-e2 raw-a2)\n[0m
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 |
--------------------------------------------------------------------------------