duct-framework / duct

Server-side application framework for Clojure
MIT License
1.13k stars 51 forks source link

Testing endpoints with additional dependencies #5

Closed kendagriff closed 9 years ago

kendagriff commented 9 years ago

What's the most appropriate way to test an endpoint component that has additional dependencies?

A standalone endpoint can be easily tested:

(def handler
  (signup/signup-endpoint {}))

(let [resp (-> (session handler) (visit "/signup") (:response))]
    (is (= 200 (:status resp))))

But how does that translate to endpoints that need, say, a database passed to its config? Shall I plan on (start)ing and (stop)ing a complete system within each test?

(P.S. Fantastic project, by the way – I'm planning to use it extensively in a microservices architecture, and I hope to contribute to it soon.)

weavejester commented 9 years ago

It depends what type of testing you're aiming to do.

If you want to perform an integration test against a real database, you can include the database component you're using in your tests. For example:

(deftest test-signup
  (let [db (component/start (hikaricp/hikaricp {...}))]
    (try
      (let [handler (signup/signup-endpoint {:db db})]
        ...)
      (finally
        (component/stop db)))))

If you want to perform a unit test, then you need some way of mocking the database calls. Fortunately, components are records, and we can therefore extend them with protocols. For example, we might use the following protocol to abstract the signup database calls:

(defprotocol SignupModel
  (create-user [db email password]))

In our real code, we implement the protocol for the database component:

(extend-protocol SignupModel
  hikaricp/HikariCP
  (create-user [{db-spec :spec} email password]
    (jdbc/insert! db-spec ...)))

While in our test, we'd either use reify or create a fake record:

(defrecord FakeDatabase
  SignupModel
  (create-user [_ _ _] 123))
kendagriff commented 9 years ago

Ah, thanks! I overlooked the obvious fact that components can be brought together without system-map. Thanks for the examples!