Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@philomates
Last active February 7, 2023 12:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save philomates/32f8e1f7a2a4e746ac0186964aaaa2d3 to your computer and use it in GitHub Desktop.
Save philomates/32f8e1f7a2a4e746ac0186964aaaa2d3 to your computer and use it in GitHub Desktop.
clojure.test re-interpretation of nubank/state-flow
(ns example-test
(:require [clojure.test :refer [is deftest testing]]
[flow :as f]
[matcher-combinators.test :refer [match?]]))
(f/tests-as-flows!)
(defn request! [verb url body]
(let [servlet (get-state #(get-in % [:system :http/server :io.pedestal.http/service-fn]))]
(-> (io.pedestal.test/response-for servlet verb url :body body)
...)))
(deftest ^{:flow-system-override f/experimental-flow-config} private-pages-are-private
(testing "if you try to access a private page while logged out you get redirected"
(is (match? {:status 302
:headers {"Location" "/login?redirect-to=/ops/"}}
(request! :get "/ops/" {}))))
(testing "once logged in you can access private pages"
(dev-login)
(is (match? {:status 200}
(request! :get "/ops/" {})))))
(ns flow
(:require [clojure.test :as t :refer [is testing]]))
;; Testing internals
;; ------------------------------------------------
(def ^{:dynamic true
:private true}
*state*
nil)
(defonce test-report-methods (methods t/report))
;; capture test results as data instead of report events
;; useful for building more tooling on top of clojure.test
(def ^:dynamic *test-results* nil)
(defn- register-test-result! [m]
(when *test-results*
(when-let [test-var (last t/*testing-vars*)]
(dosync
(commute *test-results*
update
test-var
(fnil conj [])
(assoc m :context-str t/*testing-contexts*))))))
(defmethod t/report :pass [m]
(register-test-result! m)
((get test-report-methods :pass) m))
(defmethod t/report :fail [m]
(register-test-result! m)
((get test-report-methods :fail) m)
;; interrupt the control flow by escaping back to the `deftest`, which
;; swallows the exception and aborts the rest of the test
(when *state*
(throw (ex-info "internal error" {:type ::fail}))))
(defmethod t/report :error [m]
(if (= ::fail (-> m :actual ex-data :type))
;; pass internal exceptions through
(throw (:actual m))
(do
(register-test-result! m)
((get test-report-methods :error) m))))
;; Client-specific internals
;; ------------------------------------------------
(def experimental-flow-config {:config {:datomic {:transact-experimental? true}}})
(defn start-system!
([] (start-system! {}))
([config-overrides]
(try ;; starts a test instance of the server "system"
(system/init! :test {:config-overrides config-overrides})
(catch Exception e
(when-let [system (:system (ex-data e))]
(integrant.core/halt! system))
(throw e)))))
;; API
;; ------------------------------------------------
(defn get-state
([] (get-state identity))
([f] (f @*state*)))
(defn swap-state [& args]
(apply swap! *state* args))
(defn as-flow [test-fn]
(binding [*state* (atom {:system (start-system! (-> t/*test-var* meta :flow-system-overrides))})]
(try
(test-fn)
(catch Exception e
(if (= ::fail (-> e ex-data :type))
nil
(throw e)))
(finally
(ig/halt! (:system @*state*))))))
(defn tests-as-flows! []
(t/use-fixtures :each as-flow))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment