Clojure Unit Testing with Expectations Part Two
Clojure Unit Testing with Expectations Part Three
Clojure Unit Testing with Expectations Part Four
Clojure Unit Testing with Expectations Part Five (this entry)
Clojure Unit Testing with Expectations Part Six
expectations obviously has a bias towards one assertion per test; however, there are times that verifying several things at the same time does make sense. For example, if you want to verify a few different properties of the same Java object it probably makes sense to make multiple assertions on the same instance.
One of the biggest problems with multiple assertions per test is when your test follows this pattern:
- create some state
- verify a bit about the state
- alter the state
- verify more about the state
expectations takes an alternate route - embracing the idea of multiple assertions by providing a specific syntax that allows multiple verifications and the least amount of duplication.
The following example shows how you can test multiple properties of a Java object using the 'given' syntax.
(given (java.util.ArrayList.)The syntax is simple enough: (given an-object (expect method return-value [method return-value]))
(expect
.size 0
.isEmpty true))
jfields$ lein expectations
Ran 2 tests containing 2 assertions in 4 msecs
0 failures, 0 errors.
note: [method return-value] may be repeated any number of times.
This syntax allows us to expect return-values from as many methods as we care to verify, but encourages us not to change any state between our various assertions. This syntax also allows us to to run each assertion regardless of the outcome of any previous assertion.
Obviously you could call methods that change the internal state of the object and at that point you're on your own. I definitely wouldn't recommend testing that way. However, as long as you call methods that don't change any state 'given' can help you write succinct tests that verify as many aspects of an object as you need to test.
As usual, I'll show the output for tests that fail using this syntax.
(given (java.util.ArrayList.)This specific syntax was created for testing Java objects, but an interesting side effect is that it actually works on any value and you can substitute method calls with any function. For example, you can test a vector or a map using the examples below as a template.
(expect
.size 1
.isEmpty false))
jfields$ lein expectations
failure in (core.clj:4) : sample.test.core
(expect 1 (.size (java.util.ArrayList.)))
expected: 1
was: 0
failure in (core.clj:4) : sample.test.core
(expect false (.isEmpty (java.util.ArrayList.)))
expected: false
was: true
(given [1 2 3]And, of course, the failures.
(expect
first 1
last 3))
(given {:a 2 :b 4}
(expect
:a 2
:b 4))
jfields$ lein expectations
Ran 4 tests containing 4 assertions in 8 msecs
0 failures, 0 errors.
(given [1 2 3]When you want to call methods on a Java object or call functions with the same instance over and over the previous given syntax is really the simplest solution. However, there are times where you want something more flexible.
(expect
first 2
last 1))
(given {:a 2 :b 4}
(expect
:a 1
:b 1))
jfields$ lein expectations
failure in (core.clj:4) : sample.test.core
(expect 2 (first [1 2 3]))
expected: 2
was: 1
failure in (core.clj:4) : sample.test.core
(expect 1 (last [1 2 3]))
expected: 1
was: 3
failure in (core.clj:9) : sample.test.core
(expect 1 (:a {:a 2, :b 4}))
expected: 1
was: 2
failure in (core.clj:9) : sample.test.core
(expect 1 (:b {:a 2, :b 4}))
expected: 1
was: 4
Ran 4 tests containing 4 assertions in 14 msecs
4 failures, 0 errors.
expectations also has a 'given' syntax that allows you to specify a template - thus reducing code duplication. The following example shows a test that verifies + with various arguments.
(given [x y] (expect 10 (+ x y))The syntax for this flavor of given is: (given bindings template-form values-to-be-bound). The template form can be anything you need - just remember to put the expect in there.
4 6
6 4
12 -2)
jfields$ lein expectations
Ran 3 tests containing 3 assertions in 5 msecs
0 failures, 0 errors.
Here's another example where we combine given with in to test a few different things. This example shows both the successful and failing versions.
;; successfulThat's basically it for 'given' syntax within expectations. There are times that I use all of the various versions of given; however, there seems to be a connection with using given and interacting with Java objects. If you don't find yourself using Java objects very often then you probably wont have a strong need for given.
(given [x y] (expect x (in y))
:a #{:a :b}
{:a :b} {:a :b :c :d})
;; failure
(given [x y] (expect x (in y))
:c #{:a :b}
{:a :d} {:a :b :c :d})
lein expectations
failure in (core.clj:8) : sample.test.core
(expect :c (in #{:a :b}))
key :c not found in #{:a :b}
failure in (core.clj:8) : sample.test.core
(expect {:a :d} (in {:a :b, :c :d}))
expected: {:a :d}
in: {:a :b, :c :d}
:a expected: :d
was: :b
Ran 4 tests containing 4 assertions in 13 msecs
2 failures, 0 errors.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.