Jay Fields is a software developer at DRW Trading. He has a passion for discovering and maturing innovative solutions. His most recent work has been in the Domain Specific Language space where he's delivered applications that empowered subject matter experts to write the business rules of the applications. He is also very interested in maturing software design through software testing. Jay is a DZone MVB and is not an employee of DZone and has posted 112 posts at DZone. You can read more from them at their website. View Full User Profile

Clojure: Expectations Verify Interaction Args

01.26.2013
| 1832 views |
  • submit to reddit

The expectations framework provides the ability to create interaction (or behavior) based tests. I've previously written about adding interaction based testing to expectations; however, the examples from that blog entry focused exclusively on testing interactions where each argument is matched using equality. In this entry I'll give examples of how each argument can be also be verified using a class, regex, exception, or a custom function.

When writing state based tests using expectations the type of test you're writing is inferred from the expected value. If the expected value is a regex, expectations will test the actual value to see if it matches the regex. If you passed in a class, expectations will test the actual value to see if it's an instance of that class. If you passed in an exception... you get the idea. All of what I said above, is also true for arguments of an interaction.

Let's start with a simple interaction based test:

(ns blog-expectations

(:use expectations))

 

(expect (interaction (spit "/tmp/hello-world" "some data" :append true))

(do

(spit "/tmp/somewhere-else" "nil")

(spit "/tmp/hello-world" "some data" :append true)))

In the example above, we're calling the spit function with exactly the arguments that we've specified in our test. This test will pass; however, we've had to specify the exact file location and the exact data. If for some reason you can't specify exactly what the argument will be, it's nice to have a way to specify as much as you possibly can.

In the example below, we're still specifying the exact data, but we're only verifying that the file is somewhere in /tmp/.

(expect (interaction (spit #"/tmp/" "some data" :append true))

(do

(spit "/tmp/somewhere-else" "nil")

(spit "/tmp/hello-world" "some data" :append true)))

As I previously mentioned, we can also get more general and only verify the class of an argument. For example, if we knew our data was going to be a String, but we didn't want to specify exactly what that string was, the following test would do the trick.

(expect (interaction (spit #"/tmp/" String :append true))

(do

(spit "/tmp/somewhere-else" "nil")

(spit "/tmp/hello-world" "some data" :append true)))

While expectations provides you with a lot of default options, there are times when you'll want to write your own argument "matcher". As a contrived example, let's pretend that we want to test that the last argument is true or nil.

(defn true-or-nil? [x]

(or (true? x) (nil? x)))

 

;; this test passes

(expect (interaction (spit #"/tmp/" String :append true-or-nil?))

(do

(spit "/tmp/somewhere-else" "nil")

(spit "/tmp/hello-world" "some data" :append true)))

 

;; so does this test

(expect (interaction (spit #"/tmp/" String :append true-or-nil?))

(do

(spit "/tmp/somewhere-else" "nil")

(spit "/tmp/hello-world" "some data" :append nil)))

 

;; this test fails

(expect (interaction (spit #"/tmp/" String :append true-or-nil?))

(do

(spit "/tmp/somewhere-else" "nil")

(spit "/tmp/hello-world" "some data" :append "not true or nil")))

One of the best features of expectations is it's error reporting, and the same error reporting logic is applied to arguments when an interaction based test fails. Given the example above, you'll get the following error message.

failure in (success_examples.clj:204) : success.success-examples
           expected: (spit #"/tmp/" String :append true-or-nil?) 
                got: 0 times 

           -- got: (spit "/tmp/somewhere-else" "nil")
           "nil", "/tmp/somewhere-else" are in actual, but not in expected
           true_or_nil_QMARK, #"/tmp/", :append, String are in expected, but not in actual
           expected is larger than actual 

           -- got: (spit "/tmp/hello-world" "some data" :append "s")
           - arg4: not true or nil

As you can see both calls are reported, and each argument has a detailed report (if it did not match).

Finally, expectations provides and additional function that can be used to verify that certain key/value pairs are in an argument. The following example doesn't really make sense, since you'd never want to pass a map as the last argument to spit, but it's easy to follow in the context of this blog entry.

(expect (interaction (spit String #"some da" keyword? (contains-kvs :a :b :c :d)))

(spit "/tmp/hello-world" "some data" :append {:a :b :c :d :e :f}))

In the above example, (contains-kvs) is used to verify that the final argument to spit contains the key/value pairs :a :b :c :d.

I hope that interaction arg matching follows the principle of least surprise, since it behaves the same as expectations state based tests. I also hope that the ability to use an arbitrary function for verification will provide any necessary flexibility. If you're using expectations, give it a try and let me know.






 

Published at DZone with permission of Jay Fields, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)