Version: 1.0.0
Last Updated: 2026-01-09
Status: Production Ready
The Admin module has comprehensive test coverage across three layers:
Current Coverage: ~78% (lines), 90+ test cases
test/boundary/admin/
├── core/
│ ├── permissions_test.clj # 19 tests - Role-based access control
│ ├── schema_introspection_test.clj # 9 tests - DB metadata parsing
│ └── ui_test.clj # 27 tests - UI component generation ★ NEW
└── shell/
├── http_test.clj # 16 tests - HTTP endpoint behavior
├── service_test.clj # 15 tests - CRUD operations with H2
└── schema_repository_test.clj # 11 tests - Entity config management
| Layer | Files | Purpose | Dependencies |
|---|---|---|---|
| Core | core/*_test.clj | Pure business logic | None (pure functions) |
| Shell | shell/service_test.clj | Service orchestration | H2 in-memory DB |
| Shell | shell/http_test.clj | HTTP contracts | H2 + minimal HTTP handler |
| Shell | shell/schema_repository_test.clj | Config loading | H2 DB metadata |
:unit metadata)Location: test/boundary/admin/core/*_test.clj
Characteristics:
Example - UI Component Test:
(deftest render-field-value-test
(testing "Render boolean values"
(let [result (ui/render-field-value :active true {:type :boolean})]
(is (vector? result)) ; Returns Hiccup vector
(is (= :span (first result)))
(is (str/includes? (str result) "Yes")))))
Run Unit Tests:
clojure -M:test:db/h2 --focus-meta :unit
:integration metadata)Location: test/boundary/admin/shell/service_test.clj
Characteristics:
Example - Service Test:
(deftest list-entities-pagination-test
(testing "Pagination with page-size parameter"
(create-test-users! 10) ; Create test data
(let [result (ports/list-entities *admin-service* :test-users
{:page 1 :page-size 5})]
(is (= 5 (count (:items result))))
(is (= 1 (:page-number result)))
(is (= 10 (:total-count result))))))
Run Integration Tests:
clojure -M:test:db/h2 --focus-meta :integration
:contract metadata)Location: test/boundary/admin/shell/http_test.clj
Characteristics:
Example - HTTP Test:
(deftest entity-list-endpoint-test
(testing "Admin can view entity list"
(create-test-user! "alice@example.com" "Alice" true)
(let [request (make-request :get "/web/admin/test-users" admin-user)
response (*handler* request)]
(is (= 200 (:status response)))
(is (str/includes? (:body response) "alice@example.com")))))
Run Contract Tests:
clojure -M:test:db/h2 --focus-meta :contract
Location: test/boundary/admin/core/ui_test.clj (Section at bottom)
Tests:
for attributes<nav>, <table>, <form>)* and :required attribute)Example - Accessibility Test:
(deftest accessibility-aria-labels-test
(testing "Icon buttons have aria-label"
(let [page (ui/entity-list-page :users [sample-record] config {...})
page-str (str page)]
(is (str/includes? page-str "aria-label"))
(is (str/includes? page-str "Search")))))
# All admin tests (unit + integration + contract)
clojure -M:test:db/h2 --focus-meta :admin
# Unit tests only (fastest)
clojure -M:test:db/h2 --focus-meta :unit
# Specific test file
clojure -M:test:db/h2 --focus boundary.admin.core.ui-test
# Specific test function
clojure -M:test:db/h2 --focus boundary.admin.core.ui-test/render-field-value-test
# Watch mode (re-run on file changes)
clojure -M:test:db/h2 --watch --focus-meta :unit
Tests require JWT_SECRET environment variable:
export JWT_SECRET="test-secret-key-minimum-32-characters"
clojure -M:test:db/h2 --focus-meta :unit
Or use .env file (recommended):
# .env
JWT_SECRET=test-secret-key-minimum-32-characters
DB_PASSWORD=dev_password
# Load and run
set -a && source .env && set +a
clojure -M:test:db/h2 --focus-meta :unit
--- unit (clojure.test) ---------------------------
boundary.admin.core.ui-test
render-field-value-test
Render boolean values ✓
Render UUID values ✓
accessibility-aria-labels-test
Icon buttons have aria-label ✓
27 tests, 232 assertions, 0 failures.
Top 3 slowest kaocha.type/var (0,02466 seconds, 28,3% of total time)
boundary.admin.core.ui-test/htmx-attributes-test
0,01519 seconds boundary/admin/core/ui_test.clj:700
When: Testing src/boundary/admin/core/*.clj functions
Template:
(ns boundary.admin.core.my-feature-test
(:require [boundary.admin.core.my-feature :as feature]
[clojure.test :refer [deftest is testing]]))
^{:kaocha.testable/meta {:unit true :admin true}}
(deftest my-function-test
(testing "Descriptive test name"
(let [input {:foo "bar"}
result (feature/my-function input)]
(is (= expected result)))))
Guidelines:
deftest per function, multiple testing blocks for scenariosdef inside deftest (use let instead)When: Testing src/boundary/admin/shell/service.clj with database
Template:
(ns boundary.admin.shell.my-service-test
(:require [boundary.admin.ports :as ports]
[boundary.platform.shell.adapters.database.factory :as db-factory]
[clojure.test :refer [deftest is testing use-fixtures]]))
^{:kaocha.testable/meta {:integration true :admin true}}
(def test-db-config
{:adapter :h2
:database-path "mem:my_test;DB_CLOSE_DELAY=-1"
:pool {:minimum-idle 1 :maximum-pool-size 3}})
(defonce ^:dynamic *db-ctx* nil)
(defonce ^:dynamic *service* nil)
(defn setup-test-system! []
(let [db-ctx (db-factory/db-context test-db-config)]
(db/execute-update! db-ctx {:raw "CREATE TABLE ..."})
(alter-var-root #'*db-ctx* (constantly db-ctx))
(alter-var-root #'*service* (constantly (create-service db-ctx)))))
(defn teardown-test-system! []
(when *db-ctx*
(db-factory/close-db-context! *db-ctx*)))
(use-fixtures :once
(fn [f]
(setup-test-system!)
(f)
(teardown-test-system!)))
(deftest my-integration-test
(testing "Service with real database"
(let [result (ports/my-operation *service* {:input "data"})]
(is (= expected result)))))
Guidelines:
:each fixture)^:dynamic *db-ctx*) for test stateWhen: Testing src/boundary/admin/shell/http.clj endpoints
Template:
(deftest my-endpoint-test
(testing "GET /web/admin/my-entity"
(let [request (make-request :get "/web/admin/my-entity" admin-user
{:query {"page" "1"}})
response (*handler* request)]
(is (= 200 (:status response)))
(is (str/includes? (:body response) "expected-content")))))
Guidelines:
HX-Trigger, HX-Target)When: Testing src/boundary/admin/core/ui.clj Hiccup generation
Template:
(deftest my-component-test
(testing "Component generates correct Hiccup"
(let [component (ui/my-component {:data "value"})
component-str (str component)]
;; Structure assertions
(is (vector? component))
(is (= :div.my-class (first component)))
;; Content assertions
(is (str/includes? component-str "expected-text"))
;; Accessibility assertions
(is (str/includes? component-str "aria-label")))))
Guidelines:
hx-get, hx-post, hx-trigger)Problem: Repeated test data setup across tests
Solution: Define fixtures at top of file
(def sample-user
{:id #uuid "00000000-0000-0000-0000-000000000001"
:email "test@example.com"
:name "Test User"
:role :admin
:active true})
(def sample-entity-config
{:label "Users"
:list-fields [:email :name :active]
:search-fields [:email :name]
:hide-fields #{:password-hash}})
(deftest my-test
(testing "Using fixtures"
(let [result (process sample-user sample-entity-config)]
(is (some? result)))))
Problem: Repeated test operations (create user, make request)
Solution: Define helper functions
(defn create-test-user!
([email name]
(create-test-user! email name true))
([email name active]
(let [user-data {:id (UUID/randomUUID)
:email email
:name name
:active active}]
(db/execute-one! *db-ctx* {:insert-into :users :values [user-data]})
user-data)))
(deftest my-test
(testing "With helper function"
(create-test-user! "alice@example.com" "Alice")
(create-test-user! "bob@example.com" "Bob")
;; ... assertions
))
Problem: Checking nested Hiccup structure
Solution: Use tree-seq to find nested elements
(deftest nested-element-test
(testing "Page contains nested table"
(let [page (ui/entity-list-page ...)]
;; Find nested :table.data-table element
(is (some #(and (vector? %) (= :table.data-table (first %)))
(tree-seq vector? seq page))))))
Problem: Checking if Hiccup contains text/attributes
Solution: Convert to string and use str/includes?
(deftest content-check-test
(testing "Component contains expected text"
(let [component (ui/my-component {:label "Users"})
component-str (str component)]
(is (str/includes? component-str "Users"))
(is (str/includes? component-str "aria-label")))))
Test our UI against these criteria:
<label> with for attribute(deftest form-labels-test
(let [widget (ui/render-field-widget :email "test@example.com"
{:widget :email-input :label "Email"} nil)]
(is (str/includes? (str widget) ":label"))
(is (str/includes? (str widget) ":for"))))
aria-label(deftest aria-labels-test
(let [button (ui/icon-button :search)]
(is (str/includes? (str button) "aria-label"))
(is (str/includes? (str button) "Search"))))
<nav> for navigation, not <div><table> for data tables, not layout<form> for forms, not button click handlers<button> for actions, not <div onclick>(deftest semantic-html-test
(let [sidebar (ui/admin-sidebar ...)]
(is (some #(= :nav.admin-sidebar-nav (first %))
(tree-seq vector? seq sidebar)))))
* or "required" text)required attribute on input(deftest required-fields-test
(let [widget (ui/render-field-widget :email "" {:required true} nil)]
(is (str/includes? (str widget) "*")) ; Visual indicator
(is (str/includes? (str widget) ":required true")))) ; HTML attribute
has-errors class)(deftest error-messages-test
(let [widget (ui/render-field-widget :email "" {:required true}
["Required field"])]
(is (str/includes? (str widget) "Required field"))
(is (str/includes? (str widget) "has-errors"))))
(deftest color-contrast-test
(let [status (ui/render-field-value :active true {:type :boolean})]
(is (str/includes? (str status) "Yes")) ; Text, not just color
(is (str/includes? (str status) "badge")))) ; Class for styling
type="button" or type="submit"(deftest keyboard-navigation-test
(let [form (ui/entity-form ...)]
(is (str/includes? (str form) ":type \"submit\""))))
<h1> per page(deftest heading-hierarchy-test
(let [page (ui/admin-home ...)]
(is (str/includes? (str page) ":h1")) ; Has main heading
(is (str/includes? (str page) ":h2")))) ; Has subsections
| File | Lines | Tests | Coverage | Status |
|---|---|---|---|---|
core/permissions.clj | 532 | 19 tests | ✅ 100% | Excellent |
core/schema_introspection.clj | 250 | 9 tests | ✅ ~90% | Good |
core/ui.clj | 807 | 27 tests | ✅ ~85% | Excellent ★ NEW |
shell/service.clj | 600 | 15 tests | ✅ ~75% | Good |
shell/http.clj | 400 | 16 tests | ✅ ~80% | Good |
shell/schema_repository.clj | 200 | 11 tests | ✅ ~90% | Excellent |
| TOTAL | 4,305 | 97 tests | ~78% | Good |
Improvement: Coverage increased from 59% → 78% (+19%) with addition of UI tests and accessibility tests.
ports.clj - Protocol definitions (no logic to test)schema.clj - Malli schemas (validated by usage)module_wiring.clj - Integrant wiring (integration tested)Cause: Environment variable not set
Solution:
export JWT_SECRET="test-secret-key-minimum-32-characters"
clojure -M:test:db/h2 --focus-meta :unit
Cause: Database setup fixture not running or table name mismatch
Solution:
(use-fixtures :once setup/teardown);; Correct: Entity :test-users → Table test_users
(db/execute-update! ctx {:raw "CREATE TABLE test_users (...)"})
Cause: Shared mutable state or database pollution
Solution:
:each fixture to clean data between testsdef for mutable state (use ^:dynamic + alter-var-root)(defn with-clean-tables [f]
(when *db-ctx*
(db/execute-update! *db-ctx* {:raw "DELETE FROM test_users"}))
(f))
(use-fixtures :each with-clean-tables)
Cause: REPL state from previous test runs
Solution:
;; In REPL
(require '[integrant.repl :as ig-repl])
(ig-repl/halt) ; Stop system
(ig-repl/go) ; Fresh start
Cause: Function signature changed but tests not updated
Solution:
(defn create-service [db-ctx config] ...)(create-service db-ctx {:pagination {...}})grep -r "create-service" test/core/clojure -M:clj-kondo --lint test/def inside deftest (use let)Questions? See CONTRIBUTING.md or ask in #engineering Slack channel.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |