8.0 KiB
name, description
| name | description |
|---|---|
| testing-conventions | Describe the way that tests should be authored, conventions, tools, helpers, superceding any conventions found in existing tests. |
Testing Conventions Skill
This skill documents the testing conventions for test/clj/auto_ap/.
Test Focus: User-Observable Behavior
Primary rule: Test user-observable behavior. If an endpoint or function makes a database change, verify the change by querying the database directly rather than asserting on markup.
other rules:
- Don't test the means of doing work. For example, if there is a middleware that makes something available on a request, don't bother testing that wrapper.
- prefer :refer testing imports, rather than :as reference
- Prefer structured edits from clojure-mcp
When to Assert on Database State
When testing an endpoint that modifies data:
- Verify the database change by querying the entity directly
- Use
dc/pullordc/qto verify the data was stored correctly
;; CORRECT: Verify the database change directly
(deftest test-create-transaction
(let [result @(post-create-transaction {:amount 100.0})]
(let [entity (dc/pull (dc/db conn) [:db/id :transaction/amount] (:transaction/id result))]
(is (= 100.0 (:transaction/amount entity))))))
;; CORRECT: Verify response status and headers
(is (= 201 (:status response)))
(is (= "application/json" (get-in response [:headers "content-type"])))
;; CORRECT: Check for expected text content
(is (re-find #"Transaction created" (get-in response [:body "message"])))
When Markup Testing is Acceptable
Markup testing (HTML/SSR response bodies) is acceptable when:
- Validating response status codes and headers
- Checking for presence/absence of specific text strings
- Verifying small, expected elements within the markup
- Testing SSR component rendering
;; ACCEPTABLE: Response codes and headers
(is (= 200 (:status response)))
(is (= "application/json" (get-in response [:headers "content-type"])))
;; ACCEPTABLE: Text content within markup
(is (re-find #"Transaction found" response-body))
;; ACCEPTABLE: Small element checks
(is (re-find #">Amount: \$100\.00<" response-body))
When to Avoid Markup Testing
Do not use markup assertions for:
- Verifying complex data structures (use database queries instead)
- Complex nested content that's easier to query
- Business logic verification (test behavior, not presentation)
Database Setup
All tests in test/clj/auto_ap/ use a shared database fixture (wrap-setup) that:
- Creates a temporary in-memory Datomic database (
datomic:mem://test) - Loads the full schema from
io/resources/schema.edn - Installs custom Datomic functions from
io/resources/functions.edn - Cleans up the database after each test
Using the Fixture
(ns my-test
(:require
[auto-ap.integration.util :refer [wrap-setup]]
[clojure.test :as t]))
(use-fixtures :each wrap-setup)
(deftest my-test
;; tests here can access the test database
)
Helper Functions
test/clj/auto_ap/integration/util.clj provides helper functions for creating test data:
Identity Helpers
;; Add a unique string to avoid collisions
(str "CLIENT" (rand-int 100000))
(str "INVOICE " (rand-int 1000000))
Test Entity Builders
;; Client
(test-client
[:db/id "client-id"
:client/code "CLIENT123"
:client/locations ["DT" "MH"]
:client/bank-accounts [:bank-account-id]])
;; Vendor
(test-vendor
[:db/id "vendor-id"
:vendor/name "Vendorson"
:vendor/default-account "test-account-id"])
;; Bank Account
(test-bank-account
[:db/id "bank-account-id"
:bank-account/code "TEST-BANK-123"
:bank-account/type :bank-account-type/check])
;; Transaction
(test-transaction
[:db/id "transaction-id"
:transaction/date #inst "2022-01-01"
:transaction/client "test-client-id"
:transaction/bank-account "test-bank-account-id"
:transaction/id (str (java.util.UUID/randomUUID))
:transaction/amount 100.0
:transaction/description-original "original description"])
;; Payment
(test-payment
[:db/id "test-payment-id"
:payment/date #inst "2022-01-01"
:payment/client "test-client-id"
:payment/bank-account "test-bank-account-id"
:payment/type :payment-type/check
:payment/vendor "test-vendor-id"
:payment/amount 100.0])
;; Invoice
(test-invoice
[:db/id "test-invoice-id"
:invoice/date #inst "2022-01-01"
:invoice/client "test-client-id"
:invoice/status :invoice-status/unpaid
:invoice/import-status :import-status/imported
:invoice/total 100.0
:invoice/outstanding-balance 100.00
:invoice/vendor "test-vendor-id"
:invoice/invoice-number "INVOICE 123456"
:invoice/expense-accounts
[{:invoice-expense-account/account "test-account-id"
:invoice-expense-account/amount 100.0
:invoice-expense-account/location "DT"}]])
;; Account
(test-account
[:db/id "account-id"
:account/name "Account"
:account/type :account-type/asset])
Common Data Setup (setup-test-data)
Creates a minimal but complete dataset for testing:
(defn setup-test-data [data]
(:tempids @(dc/transact conn (into data
[(test-account :db/id "test-account-id")
(test-client :db/id "test-client-id"
:client/bank-accounts [(test-bank-account :db/id "test-bank-account-id")])
(test-vendor :db/id "test-vendor-id")
{:db/id "accounts-payable-id"
:account/name "Accounts Payable"
:db/ident :account/accounts-payable
:account/numeric-code 21000
:account/account-set "default"}]))))
Use like:
(let [{:strs [test-client-id test-bank-account-id test-vendor-id]} (setup-test-data [])]
...)
Token Helpers
;; Admin token
(admin-token)
;; User token (optionally scoped to specific client)
(user-token) ; Default: client-id 1
(user-token client-id) ; Scoped to specific client
Example Usage
(ns my-test
(:require
[clojure.test :as t]
[auto-ap.datomic :refer [conn]]
[auto-ap.integration.util :refer [wrap-setup admin-token setup-test-data test-transaction]]))
(use-fixtures :each wrap-setup)
(deftest test-transaction-import
(testing "Should import a transaction"
(let [{:strs [client-id bank-account-id]} (setup-test-data [])
tx-result @(dc/transact conn
[(test-transaction
{:db/id "test-tx-id"
:transaction/client client-id
:transaction/bank-account bank-account-id
:transaction/amount 50.0})])]
(is (= 1 (count (:tx-data tx-result))))
;; Verify by querying the database, not markup
(let [entity (dc/pull (dc/db conn) [:transaction/amount] (:db/id tx-result))]
(is (= 50.0 (:transaction/amount entity)))))))
Note on Temp IDs
Test data often uses string-based temp IDs like "client-id", "bank-account-id", etc. When transacting, the returned :tempids map maps these symbolic IDs to Datomic's internal entity IDs:
(let [{:strs [client-id bank-account-id]} (:tempids @(dc/transact conn txes))]
...)
Memory Database
All tests use datomic:mem://test - an in-memory database. This ensures:
- Tests are fast
- Tests don't interfere with each other
- No setup required to run tests locally
The database is automatically deleted after each test completes.
running tests
prefer to use clojure nrepl evaluation skill over leiningen, but worst case, use leiningen to run tests