Mocking with Midje

So I was playing around with an application recently and wanted to integrate Chas Emerick’s excellent Friend authentication library. I setup a simple User service to get users, roles etc in a format that could be consumed by Friend and wanted to test the code using Midje. The question was how to represent the user data repository ?

One option was to setup a test database usaing H2 or something similar and testing the code against it, but that would mean having to reload the database each time the tests were run to ensure the data was consistent between test runs. Another option was to store the data as a map in memory, but that seemed to have the same limitations as a test database. In the Java world we’d get around these sorts of limitations using a mocking framework like jMock or EasyMock, but how could I do this in Clojure ?

Fortunately Midje provides some great support for doing this kind of thing through  prerequisites and meta-constants. Prerequisites are great, they allow us to specify the return value of a function without having to specify the implementation. For example, let’s say I have a function get-user-roles that returns the roles for a given user as a set which in turn calls a function retrieve-user-roles that gets the user data from a database and we want to test get-user-roles without having to worry about the database, well in this case we can use a prerequisite to mock retrieve-user-roles. Here’s what our test could look like:

(fact "`get-user-roles` returns a set of roles for a user"
(get-user-roles ..user-id..) => #{..user..}
(provided (retrieve-user-roles ..user-id..) => [{:name ..user..}]))

Here we specify our test case as:

(get-user-roles ..user-id..) => #{..user..}

which basically says that if we call get-user-roles passing in ..user-id.. the result should be a set containing ..user.. . The important part is:

(provided (retrieve-user-roles ..user-id..) => [{:name ..user..}]

This is our prerequisite, specified by the (provided) form, and it basically says that when retrieve-user-roles is called with a parameter of ..user-id.. it will return a vector containing a map of {:name ..user..}. Another cool feature of prerequisites is that when we specify a (provided) form not only are we specifying the return value, but also that the code under test must call the function in the prerequisite with the stated parameters. If it doesn’t then the test will fail.

Now some of you may have noticed the odd parameters that were used in the test case, e.g. ..user-id.. . This is an example of a meta-constant, the second thing that Midje provides to assist in mocking. Meta-constants allow us to defer decisions about what data we want to use in our tests, essentially they allow us to substitute the data for a symbol and then refer to the symbol rather than having to worry about the actual value. For example, in the sample code above we pass a meta-constant, ..user-id.., to get-user-roles rather than passing in an actual user ID since we don’t really care about what value is passed to the function only that when ..user-id.. is passed a specific result should be returned. True in this instance we could hard code a value in the test, but using a meta-constant gives us a couple of advantages. Firstly it makes explicit that we aren’t concerned with the actual value that is passed to the function whilst making it clear the value under test is a user ID and secondly it makes it easier to catch typos and errors where the meta-constant is used as the test will fail if the meta-constant name is wrong or used inconsistently within the test.

As a further example suppose we want now to test admin accounts. In this case we can easily write a second test passing in a new meta-constant ..admin-id.. to the get-user-roles function and add a new prerequisite to return a different set of data for admins. Our test can now expect a different set of roles to be returned without having to change any core code or worry about what data is in the database. Magic.

This entry was posted in Clojure and tagged . Bookmark the permalink.