Testing Datomic queries and pulls using d/with
There’s amazing functionality for unit or integration testing Datomic queries and pulls that ships with the datomic.api library. d/with applies transaction data to a provided database without mutating the underlying source. This gives the ability to ask “what if?” style questions to existing data, but also facilitates setting an instance of a Datomic database to a known state. This can then be used to test queries and pulls that return expected values.
A simple contrived example of testing a query that returns the people over the age of 30:
(let [db (-> (d/db conn)
(d/with [{:db/id (d/tempid :db.part/user)
:user/email "test@example.com"
:age 29}
{:db/id (d/tempid :db.part/user)
:user/email "bob@example.com"
:age 33}])
:db-after)]
(is (= 1 (number-of-people-over-30 db))))
The underlying number-of-people-over-30 Datomic query is responsible for finding the people who have an :age over 30 and performing a count on the result.
Another example is the use of w/with and Datomic pull syntax to get the friends of a user. The example assumes a user email can be used as a lookup ref.
(let [db (-> (d/db conn)
(d/with [{:db/id (d/tempid :db.part/user)
:user/email "test@example.com"
:user/friends []}
{:db/id (d/tempid :db.part/user)
:user/email "bob@example.com"
:user/friends [{:user/id 9}
{:user/id 18}
{:user/id 37]}]])
:db-after)]
(is (= {:user/friends [{:user/id 9} {:user/id 18} {:user/id 37}]}
(d/pull db [{:user/friends [:user/id]}] [:user/email "bob@example.com"]))))
As you can see from the examples above, d/with takes a collection of transaction data. Providing you’re separating the creation of transaction data from the transacting then d/with is a great place to test functions that create transaction data. You can also apply the transaction data to an existing database and ask what if questions (projections).
(-> (d/db conn)
(d/with [(create-comment-tx {:article/id "933b1d81-14e4-409a-a6c4-70e75dc61150"
:comment/text "This is a great article!"})]))
I create an in-memory Datomic database and seed it for testing purposes. My test suite runs (unscientifically measured) quickly and I have confidence in my code.