I've always been weary of writing integration tests in the manner of unit tests because when they fail they're difficult to diagnose. All that a failed unit-style integration test tells us is that something, somewhere, broke down. In an ideal world, you would also have a unit test, which when an integration test fails, will also fail. However, rare is the team filled with unit test passion such that a beautiful pairing of failures exist. So, while we will see how we can use James Reeves' ring-mock
to do a full-fledged integration test, we must also keep in mind that integration tests on their own are not enough.
For this example, we are going to add an additional test to the hipstr.test.handler
namespace that was generated by Luminus when we generated our project. You'll see something like this:
(ns hipstr.test.handler (:use clojure.test ring.mock.request hipstr.handler)) (deftest test-app (testing "main route" (let [response (app (request :get "/"))] (is (= 200 (:status response))))) (testing "not-found route" (let [response (app (request :get "/invalid"))] (is (= 404 (:status response))))))
We've not executed any of the tests from this namespace yet, but if you recall, simply running the lein test
, without specifying any namespaces will run all tests under the hipstr.test
namespace. Alternatively, if you change the namespace to end in test
, the lein quickie
plugin will also execute it.
The only real difference between how we construct the integration tests and the unit tests is that we use the ring.mock.request
function. This function will actually construct a valid request map for the given HTTP method and URI, and any parameters we want to provide to the endpoint. Afterwards, the ring.mock.request
function runs that request map through our stack, executing everything along the matching route handler.
In that spirit, we can test and ensure that our /signup POST
route, will not return a 302 redirect to the /signup-success GET
route unless all of the parameters (:email
, :username
, and :password
) are valid. We'll construct another test context, and create the first assertion—that a missing e-mail returns a 200 response OK
instead of a 302 redirect. Use following lines of code to do this:
(deftest missing-email-address-redisplays-the-form (let [response (app (request :post "/signup" {:username "TheDude" :password "123456789"}))] (is (= 200 (:status response)))))
This test will actually invoke our /signup POST
route in our home-routes
, and drill all the way through the plumbing, including rendering the response. Note that integration tests will also interact with any external, dependent systems, such as the database. As such, it's important that you pay pay close attention to what your integration test interacts with; if it mutates any data, you must reset that data prior to running the next test. For this reason, we will not test for a successful 302 redirect because this would ultimately pollute our database.
Unit testing is an academic pursuit well beyond the means of this book. If you are interested in getting deep into unit testing, I highly recommend that you read Gerard Meszaros' xUnit Test Patterns, a book with an incredible wealth of knowledge when it comes to writing sustainable automated tests.
3.15.231.194