Name: testit
Owner: Metosin
Description: Midje like assertions for Clojure.test
Created: 2015-10-21 20:29:00.0
Updated: 2018-05-06 00:21:22.0
Pushed: 2018-04-28 14:46:50.0
Size: 97
Language: Clojure
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
Midje style assertions for clojure.test
Clojars dependency: [metosin/testit "0.2.0"]
Note: This library is still under heavy development!
Goals:
=>
and =not=>
clojure.test
Non-goals:
with Midje:
some.midje.tests
require [midje.sweet :refer :all]))
ts
1 2) => 3
a 1 :z 1} => (contains {:a 1}))
with testit
:
example.midje-example
require [clojure.test :refer :all]
[testit.core :refer :all]))
test midje-impersonation
acts
(+ 1 2) => 3
{:a 1 :z 1} =in=> {:a 1}))
The fact
macro generates a clojure.test/is
form with the arrow in
place of test function.
roexpand-1 '(fact (+ 1 2) => 3))
clojure.test/is (=> 3 (+ 1 2)) "(+ 1 2) => 3")
The testit
extends basic clojure.test/is
functionality
by adding appropriate assertion logic for =>
, =not=>
, =in=>
,
and =throws=>
symbols.
The facts
allows grouping of multiple assertions into a single form. For
example:
test group-multiple-assertions
acts "simple match tests"
(+ 1 2) => 3
(* 21 2) => 42
(+ 623 714) => 1337))
=>
and =not=>
arrowsThe left side of =>
arrow is a form that is evaluated once. The right side
can be a simple form that is also evaluated once. Test is performed by
comparing the evaluated values for equality (or non-equality in case of
=not=>
).
test simple-form
acts
(* 21 2) => 42))
The right side of =>
can also be a predicate function. In that case the
test is performed by passing the value of the left side to the predicate.
test function-predicate
acts
(* 21 2) => integer?))
A common practice is to call function that returns a predicate. For example:
test generate-predicate
acts
(* 21 2) => (partial > 1337)))
A bit more complex example:
n close-enough [expected-value margin]
n [result]
(<= (- expected-value margin) result (+ expected-value margin))))
test function-generating-predicate
acts
(* 21 2) => (close-enough 40 5)))
=throws=>
arrowThe =throws=>
arrow can be used to assert that the evaluation of the left side
throws an exception. The right side of =throws=>
can be:
java.lang.Throwable
java.lang.Throwable
If the right side is a class, the assertion is made to ensure that the left side throws an exception that is, or extends, the class on the right side.
t "Match exception class"
1 0) =throws=> java.lang.ArithmeticException)
You can also use an exception object. This ensures that the exception is of correct type, and also that the message of the exception equals that of the message on right side.
t "Match exception class and message"
1 0) =throws=> (java.lang.ArithmeticException. "Divide by zero"))
Most flexible case is to use a predicate function.
t "Match against predicate"
1 0) =throws=> #(-> % .getMessage (str/starts-with? "Divide")))
Finally, =throws=>
supports a sequence. The thrown exception is tested
against the first element from the seq, and it's cause to the second, and
so on. This is very handy when the actual exception you are interested is
wrapped into another exception, for example to
java.util.concurrent.ExecutionException
.
This example creates an java.lang.ArithmeticException
, wrapped into a
java.lang.RuntimeException
, wrapped into a
java.util.concurrent.ExecutionException
.
The fact
then tests that the left side throws an exception that extends
java.lang.Exception
and has a message "1"
, and that is caused by another
exception, also extending java.lang.Exception
with message "2"
, that is
caused yet another exception, this time with message "3"
:
t
>> (java.lang.ArithmeticException. "3")
(java.lang.RuntimeException. "2")
(java.util.concurrent.ExecutionException. "1")
(throw))
hrows=> [(Exception. "1")
(Exception. "2")
(Exception. "3")])
A common pattern with Clojure code is to generate exceptions with
clojure.core/ex-info
. To help testing these kind of exceptions, testit
provides a function testit.core/ex-info?
. The function accepts a message
(string or a predicate) and a data (map or a predicate), and returns a predicate
that tests given exception type (must extend clojure.lang.IExceptionInfo
),
message and data.
t "Match ex-info exceptions"
hrow (ex-info "oh no" {:reason "too lazy"}))
hrows=>
x-info? "oh no" {:reason "too lazy"}))
The above test ensures that the left side throws an ex-info
exception with
expected message and data.
any
The any
is a predicate that matches anything. It's implemented like
this:
ns testit.core:
any (constantly true))
truthy
and falsey
Other helper predicate are testit.core/truthy
and testit.core/falsey
which test given values for clojure 'truthines' and 'falsines' respectively.
=eventually=>
arrowYou can use the =eventually=>
arrow to test async code.
[a (atom -1)]
uture
(Thread/sleep 100)
(reset! a 1))
act
(deref a) =eventually=> pos?))
On the code above, the result of evaluating the (deref a)
is initially -1.
The test does not match the expected predicate pos?
. How ever, the
=eventually=>
will keep repeating the test until the test matches, or a
timeout occurs. Eventually the future resets the atom to 1 and the test
passes.
By default the =eventually=>
keeps evaluating and testing every 50 ms and
the timeout is 1 sec. You can change these by binding
testit.core/*eventually-polling-ms*
and
testit.core/*eventually-timeout-ms*
. For example, code below sets the
timeout to 2 sec.
ting "You can change the timeout from it's default of 1sec"
inding [*eventually-timeout-ms* 2000]
(let [a (atom -1)]
(future
(Thread/sleep 1500)
(reset! a 1))
(fact
(deref a) =eventually=> pos?))))
=in=>
The =in=>
is for more relaxed equality tests, like contains
in Midje. For example:
t
a 1 :b 2} => {:a 1}) ;=> FAILS
lj
t
a 1 :b 2} =in=> {:a 1}) ;=> Ok
This ensures that the actual (left side of =in=>
) contains everything the expected (right
side) contains.
contains
When given a map, =in=>
checks that all given keys are found and they match
expectations. Expected map can be nested and can contain predicates. For example:
t
a 1
b {:c 42
:d {:e "foo"}}}
n=>
b {:c pos?
:d {:e string?}}})
A very common use-case for this kind of testing is testing the HTTP responses. Here's an example.
example.http-example
require [clojure.test :refer :all]
[testit.core :refer :all]
[clojure.string :as str]
[clj-http.client :as http]))
test test-google-response
act
(http/get "http://google.com")
=in=>
{:status 200
:headers {"Content-Type" #(str/starts-with? % "text/html")}
:body string?}))
=in=>
When given a vector, =in=>
checks that the
expected value contains matches for each expected value, where the expected
values can be basic values or predicates. For example:
t
2 3] =in=> [1 pos? integer?])
The matching is recursive, so this works too:
t
:a 1, :b 1}
:a 2, :b 2}
:a 3, :b 3}]
:a 1}, map?, {:b pos?}])
If the expectation vector ends with symbol ...
, the actual vector can
contain more elements, they are just ignored. For example, these tests all
pass:
ts
2 3] =in=> [1 2 3 ...]
2 3 4] =in=> [1 2 3 ...]
2 3 4 5] =in=> [1 2 3 ...])
This does not pass:
t
2] =in=> [1 2 3 ...]) ;=> FAILS
If the expected vector has metadata :in-any-order
, the =in=>
checks that
all expected elements are found in actual, but they are allowed to be in any
order.
ts
1 0 +1] =in=> ^:in-any-order [0 +1]
1 0 +1] =in=> ^:in-any-order [pos? neg?]))
The =in=>
tries to provide informative error messages. For example:
t
a {:b {:c -1}}} =in=> {:a {:b {:c pos?}}})
in (failing-test) (in_example.clj:52)
{:b {:c -1}}} =in=> {:a {:b {:c pos?}}}:
[:a :b :c]:
os? -1) => false
expected: pos?
actual: -1
cted: {:a {:b {:c pos?}}}
tual: {:a {:b {:c -1}}}
The in [:a :b :c]
in above error message shows the path to nested element
that failed the test.
Here's another example with sequential values:
t
1 2 3] =in=> [0 1 42 3])
in (failing-test) (in_example.clj:54)
2 3] =in=> [0 1 42 3]:
[2]:
42 2) => false
expected: 42
actual: 2
cted: [0 1 42 3]
tual: [0 1 2 3]
Strings have special reporting:
t
oodar" => "foobar")
in (failing-test) (in_example.clj:56)
ar =in=> foobar:
(= "foobar" "foodar") => false
expected: "foobar"
actual: "foodar"
diff: ^^^
cted: "foobar"
tual: "foodar"
Here's an example with nested structures:
t
a {:b [0 1 2 {:c "foodar"}]}}
n=>
a {:b [0 neg? 2 {:c "foobar"}]}})
in (failing-test) (in_example.clj:58)
{:b [0 1 2 {:c "foodar"}]}} =in=> {:a {:b [0 neg? 2 {:c "foobar"}]}}:
[:a :b 1]:
eg? 1) => false
expected: neg?
actual: 1
[:a :b 3 :c]:
"foobar" "foodar") => false
expected: "foobar"
actual: "foodar"
diff: ^^^
cted: {:a {:b [0 neg? 2 {:c "foobar"}]}}
tual: {:a {:b [0 1 2 {:c "foodar"}]}}
Note that the above message reports two errors, first at [:a :b 1]
and
the second at [:a :b 3 :c]
.
The testit
is designed to be easily extendable using plain old functions. You
can provide your own predicates, and combine your predicates with those provided
by clojure.core
and testit
.
Here's an example that combines contains
and ex-info?
:
t "Match ex-info exceptions with combine"
hrow (ex-info "oh no" {:reason "too lazy"}))
hrows=>
x-info? any (contains {:reason string?})))
This tests that the left side throws an ex-info
exception, with any
message and a data that contains at least a :reason
that must be a string.
testit
with your own arrowsYou can add your custom arrows using clojure.test/assert-expr
. For example,
here's a simple extension that asserts that the test is completed within 1 sec.
testit.your-own-arrow
require [clojure.test :refer :all]
[testit.core :refer :all]))
lare =quickly=>)
method assert-expr '=quickly=> [msg [_ & body]]
ssert-expr msg `(let [d# (future ~@body)
r# (deref d# 1000 ::timeout)]
(if (= r# ::timeout)
false
r#))))
And then you use it like this:
test things-must-be-fast-tests
act
(do (Thread/sleep 200) 42) =quickly=> 42) ;=> PASS
act
(do (Thread/sleep 2000) 42) =quickly=> 42)) ;=> FAIL
In the example above, the first fact passes but the second fails after 1 sec.
=eventually=>
for async tests=>
forms with fact
Copyright © 2017 Metosin Oy
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.