duct icon indicating copy to clipboard operation
duct copied to clipboard

Duct tests with integrant

Open zerg000000 opened this issue 7 years ago • 3 comments

The Duct unit test example only cover init a handler with no dependences which is impossible in real world. Currently, if I want to do integration test with db. I need to do something like

(defn config-fixture [keys deps]
  (fn [f]
    (let [config (-> (duct/read-config (io/resource "test.edn"))
                     duct/prep
                     (dissoc :duct.core/handler))] ; remove handler because it will start the jetty
      (let [sys (ig/init config keys)]
        (ig/run! sys keys (fn [_ db]
                            (reset! deps db)
                            (f)
                            (reset! deps nil)
                            (ig/halt! sys keys)))))))

(def system (atom nil)) ; this is the trick to use ig dependents

(use-fixtures :once (fixtures/config-fixture
                      [:duct.database/sql]
                      system))

(deftest db-itest
    (testing "create dumb in db"
        (let [dumbs (-> (s/gen :dumb/dumb)
                        (gen/sample 100))
              db-spec @system
              apply-dissoc #(apply dissoc %1 %2)]
            (is db-spec "db-spec should exists")
            (doseq [dumb dumbs]
                (let [expected dumb
                       actual (db/create db-spec dumb)]
                    (is (= expected actual) "inserted dumb should be same as input"))))))

is there any better method to test with integrant?

The primary use case is for testing all the boundaries in duct, to see if they are healthy.

zerg000000 avatar Jul 13 '17 01:07 zerg000000

Ideally in Duct you'll have three types of tests:

  • System tests that init the entire system and test it from end to end
  • Integration tests that test the boundaries against the database
  • Unit tests that use mocks or stubs of the boundary protocols

So my initial advice is to test the boundary functions directly:

(deftest db-test
  (jdbc/with-db-transaction [tx db-spec]
    (jdbc/db-set-rollback-only! tx)
    (seed-database tx)
    (let [db (->Boundary tx)]
      ...)))

If you want to test boundary functions in conjunction with your handler, perhaps because you're uncertain of the interactions between the handler and the database, then you could use init-key directly:

(deftest db-test
  (jdbc/with-db-transaction [tx db-spec]
    (jdbc/db-set-rollback-only! tx)
    (seed-database tx)
    (let [db (->Boundary tx)
          handler (init-key :foo.handler/bar {:db db})]
      ...)))

Obviously you can wrap a lot of the above in a macro or function to automate the process of setting up the transaction and seeding the database.

weavejester avatar Jul 13 '17 11:07 weavejester

I would consider my tests falling into second case, that's why we will have code that's only pick the keys needed. As the example you provided, seed-database would be some calls to ragtime, since the ragtime configuration is a bit duct-ized in config.edn, if we don't touch the integrant for migrator.ragtime we needs to add some code that's similar to migrator.ragtime to seed the database.

zerg000000 avatar Jul 14 '17 02:07 zerg000000

That makes sense. In which case, rather than using init-key, you can use a selective init.

(deftest db-test
  (jdbc/with-db-transaction [tx db-spec]
    (jdbc/db-set-rollback-only! tx)
    (let [config  (assoc test-config :duct.database/sql {:connection tx})
          system  (-> config duct/prep (ig/init [:foo.handler/bar]))
          handler (:foo.handler/bar system)]
      ...)))

weavejester avatar Jul 15 '17 18:07 weavejester