--- name: testing-conventions description: 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**: 1. 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. 2. prefer :refer testing imports, rather than :as reference 3. Prefer structured edits from clojure-mcp ### When to Assert on Database State When testing an endpoint that modifies data: 1. Verify the database change by querying the entity directly 2. Use `dc/pull` or `dc/q` to verify the data was stored correctly ```clojure ;; 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 ```clojure ;; 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: 1. Creates a temporary in-memory Datomic database (`datomic:mem://test`) 2. Loads the full schema from `io/resources/schema.edn` 3. Installs custom Datomic functions from `io/resources/functions.edn` 4. Cleans up the database after each test ## Using the Fixture ```clojure (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 ```clojure ;; Add a unique string to avoid collisions (str "CLIENT" (rand-int 100000)) (str "INVOICE " (rand-int 1000000)) ``` ### Test Entity Builders ```clojure ;; 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: ```clojure (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: ```clojure (let [{:strs [test-client-id test-bank-account-id test-vendor-id]} (setup-test-data [])] ...) ``` ### Token Helpers ```clojure ;; 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 ```clojure (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: ```clojure (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