Skip to content

Commit

Permalink
Implement before-each and after-each
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahTheDuke committed Sep 23, 2024
1 parent 9d04097 commit 89dfb6c
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 49 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
(= [:before :after] @state))))
```

Additionally, add the macro `around`, which works like a clojure.test fixture:
- Add the macro `around`, which works like a clojure.test fixture:

```clojure
(describe "it works"
Expand All @@ -27,6 +27,8 @@ Additionally, add the macro `around`, which works like a clojure.test fixture:
...)
```

- Add macros `before-each` and `after-each`, which are run before/after each nested test case.

### Changes

- Changed primary branch from `master` to `main`.
Expand Down
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,31 +193,43 @@ To partition your test suite based on metadata, you can use `-i`/`--include` to

## Setup and Teardown

To handle set up and tear down of stateful architecture, Lazytest provides the hooks `(before)`, `(after)`, and `(around)`. You can add them to a `:context` vector in suite or test-case metadata:
To handle set up and tear down of stateful architecture, Lazytest provides the hooks `(before)`, `(before-each)`, `(after-each)`, `(after)`, and `(around)`. You can add them to a `:context` vector in suite or test-case metadata:

```clojure
(defdescribe before-and-after-test
(given [state (volatile! [])]
(describe "before and after"
(describe "before and after example"
{:context [(before (vswap! state conj :before))
(after (vswap! state conj :after))]}
(expect-it "temp" true))
(expect-it "temp" (vswap! state conj :expect)))
(expect-it "has been properly tracked"
(= [:before :after] @state))))
(= [:before :expect :after] @state))))

(defdescribe around-test
(given [state (volatile! [])]
(describe "around"
(describe "around example"
{:context [(around [f]
(vswap! state conj :around-before)
(f)
(vswap! state conj :around-after))]}
(expect-it "temp" true))
(expect-it "correctly ran the whole thing"
(= [:around-before :around-after] @state))))

(defdescribe each-test
(given [state (volatile! [])]
(describe "each examples"
{:context [(before (vswap! state conj :before))
(before-each (vswap! state conj :before-each))]}
(expect-it "temp" (vswap! state conj :expect-1))
(expect-it "temp" (vswap! state conj :expect-2)))
(expect-it "has been properly tracked"
(= [:before :before-each :expect-1 :before-each :expect-2] @state))))
```

Context functions of the same kind are run in the order they're defined. `(around)` hooks are combined with the same logic as `clojure.test`'s `join-fixtures`. When executing a given suite or test-case, all `(before)` hooks are run, then the `(around)` hooks are called on the nested tests (if they exist), then the `(after)` hooks are run.
`(around)` hooks are combined with the same logic as `clojure.test`'s `join-fixtures`.

Context functions of the same kind are run in the order they're defined. When executing a given suite or test-case, all `(before)` hooks are run once, then each `before-each` hook is run, then the `(around)` hooks are called on the nested tests (if they exist), then each `(after-each)` hook is run, then all `(after)` hooks are run once.

## Output

Expand Down
22 changes: 22 additions & 0 deletions src/clojure/lazytest/context.clj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@
:when (fn? before-fn)]
(before-fn)))

(defn run-before-eachs
[obj]
(doseq [before-each-fn (-> obj :context :before-each)
:when (fn? before-each-fn)]
(before-each-fn)))

(defn run-after-eachs
[obj]
(doseq [after-each-fn (-> obj :context :after-each)
:when (fn? after-each-fn)]
(after-each-fn)))

(defn run-afters
[obj]
(doseq [after-fn (-> obj :context :after)
Expand All @@ -29,3 +41,13 @@
[obj]
(when-let [arounds (-> obj :context :around seq)]
(c.t/join-fixtures arounds)))

(defn propagate-eachs
[parent-meta child]
(let [child-meta (meta child)
updated-meta (-> child-meta
(assoc-in [:context :before-each] (into (vec (-> parent-meta :context :before-each))
(-> child-meta :context :before-each)))
(assoc-in [:context :after-each] (into (vec (-> child-meta :context :after-each))
(-> parent-meta :context :after-each))))]
(with-meta child updated-meta)))
8 changes: 8 additions & 0 deletions src/clojure/lazytest/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@
[& body]
`{:before (fn before# [] (let [ret# (do ~@body)] ret#))})

(defmacro before-each
[& body]
`{:before-each (fn before-each# [] (let [ret# (do ~@body)] ret#))})

(defmacro after-each
[& body]
`{:after-each (fn after-each# [] (let [ret# (do ~@body)] ret#))})

(defmacro after
"Returns a context whose teardown method evaluates body."
[& body]
Expand Down
25 changes: 15 additions & 10 deletions src/clojure/lazytest/runner.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
(ns lazytest.runner
(:require
[lazytest.context :refer [run-afters run-befores combine-arounds]]
[lazytest.context :refer [run-afters run-befores combine-arounds run-before-eachs run-after-eachs propagate-eachs]]
[lazytest.filter :refer [filter-tree]]
[lazytest.find :refer [find-suite find-var-test-value]]
[lazytest.malli]
Expand All @@ -27,10 +27,11 @@
(update ::depth #(if id (inc %) %))
(update ::suite-history conj sm))
f (if-let [around-fn (combine-arounds sm)]
#(let [ret (volatile! nil)]
(around-fn (fn [] (vreset! ret (run-test config %))))
#(let [ret (volatile! nil)
tests (propagate-eachs sm %)]
(around-fn (fn [] (vreset! ret (run-test config tests))))
@ret)
#(run-test config %))
#(run-test config (propagate-eachs sm %)))
results (vec (keep f s))
duration (double (- (System/nanoTime) start))]
(-> (suite-result s results)
Expand Down Expand Up @@ -87,12 +88,16 @@
start (System/nanoTime)]
(report config (assoc tc-meta :type :begin-test-case))
(run-befores tc-meta)
(let [f (if-let [around-fn (combine-arounds tc-meta)]
#(let [ret (volatile! nil)]
(around-fn (fn [] (vreset! ret (try-test-case %))))
@ret)
try-test-case)
results (f tc)
(let [results (if-let [around-fn (combine-arounds tc-meta)]
(let [ret (volatile! nil)]
(run-before-eachs tc-meta)
(around-fn (fn [] (vreset! ret (try-test-case tc))))
(run-after-eachs tc-meta)
@ret)
(do (run-before-eachs tc-meta)
(let [ret (try-test-case tc)]
(run-after-eachs tc-meta)
ret)))
duration (double (- (System/nanoTime) start))
results (assoc results ::duration duration)]
(report config results)
Expand Down
182 changes: 151 additions & 31 deletions test/clojure/lazytest/context_test.clj
Original file line number Diff line number Diff line change
@@ -1,67 +1,187 @@
(ns lazytest.context-test
(:require
[lazytest.core :refer [after before defdescribe describe expect-it given around it expect]]))
[lazytest.context :refer [propagate-eachs]]
[lazytest.core :refer [after after-each around before before-each
defdescribe describe expect expect-it given it]]))

(defn vconj! [volatile value]
(vswap! volatile conj value))

(defdescribe context-test
(describe "on suites"
(given [state (volatile! [])]
(describe "manual maps"
{:context [{:before (fn [] (vswap! state conj :before))
:after (fn [] (vswap! state conj :after))}]}
(expect-it "temp" true))
{:context [{:before (fn [] (vconj! state :before))
:after (fn [] (vconj! state :after))}]}
(expect-it "temp" (vconj! state :expect)))
(expect-it "tracks correctly"
(= [:before :after] @state)))
(= [:before :expect :after] @state)))
(given [state (volatile! [])]
(describe before
{:context [(before (vswap! state conj :before))]}
(expect-it "temp" true))
{:context [(before (vconj! state :before))]}
(expect-it "temp" (vconj! state :expect)))
(expect-it "tracks correctly"
(= [:before] @state)))
(= [:before :expect] @state)))
(given [state (volatile! [])]
(describe after
{:context [(after (vswap! state conj :after))]}
(expect-it "temp" true))
{:context [(after (vconj! state :after))]}
(expect-it "temp" (vconj! state :expect)))
(expect-it "tracks correctly"
(= [:after] @state)))
(= [:expect :after] @state)))
(given [state (volatile! [])]
(describe "not order dependent"
{:context [(after (vswap! state conj :after))
(before (vswap! state conj :before))]}
(expect-it "temp" true))
{:context [(after (vconj! state :after))
(before (vconj! state :before))]}
(expect-it "temp" (vconj! state :expect)))
(expect-it "tracks correctly"
(= [:before :after] @state)))
(= [:before :expect :after] @state)))
(given [state (volatile! [])]
(describe "around"
{:context [{:around (fn [f]
(vswap! state conj :around-before)
(vconj! state :around-before)
(f)
(vswap! state conj :around-after))}]}
(expect-it "temp" true))
(vconj! state :around-after))}]}
(expect-it "temp" (vconj! state :expect)))
(expect-it "tracks correctly"
(= [:around-before :around-after] @state)))
(= [:around-before :expect :around-after] @state)))
(given [state (volatile! [])]
(describe around
{:context [(around [f]
(vswap! state conj :around-before)
(vconj! state :around-before)
(f)
(vswap! state conj :around-after))]}
(expect-it "temp" true))
(vconj! state :around-after))]}
(expect-it "temp" (vconj! state :expect)))
(expect-it "tracks correctly"
(= [:around-before :around-after] @state))))
(= [:around-before :expect :around-after] @state)))
(describe before-each
(given [state (volatile! [])]
(describe "inner"
{:context [(before-each (vconj! state :before-each))]}
(expect-it "temp 1" (vconj! state :expect-1))
(expect-it "temp 2" (vconj! state :expect-2))
(expect-it "temp 3" (vconj! state :expect-3)))
(expect-it "tracks correctly"
(= [:before-each :expect-1 :before-each :expect-2 :before-each :expect-3] @state))))
(describe after-each
(given [state (volatile! [])]
(describe "inner"
{:context [(after-each (vconj! state :after-each))]}
(expect-it "temp 1" (vconj! state :expect-1))
(expect-it "temp 2" (vconj! state :expect-2))
(expect-it "temp 3" (vconj! state :expect-3)))
(expect-it "tracks correctly"
(= [:expect-1 :after-each :expect-2 :after-each :expect-3 :after-each] @state))))
(describe "both before-each and after-each"
(given [state (volatile! [])]
(describe "inner"
{:context [(before-each (vconj! state :before-each))
(after-each (vconj! state :after-each))]}
(expect-it "temp 1" (vconj! state :expect-1))
(expect-it "temp 2" (vconj! state :expect-2)))
(expect-it "tracks correctly"
(= [:before-each :expect-1 :after-each :before-each :expect-2 :after-each] @state))))
(describe "complex flat case"
(given [state (volatile! [])]
(describe "inner"
{:context [(before (vconj! state :before))
(before-each (vconj! state :before-each))
(after-each (vconj! state :after-each))
(after (vconj! state :after))]}
(expect-it "temp 1" (vconj! state :expect-1))
(expect-it "temp 2" (vconj! state :expect-2)))
(expect-it "tracks correctly"
(= [:before :before-each :expect-1 :after-each :before-each :expect-2 :after-each :after] @state)))))
(describe "on test cases"
(given [state (volatile! [])]
(it "works correctly"
{:context [(before (vswap! state conj :before))
(after (vswap! state conj :after))]}
(expect true))
{:context [(before (vconj! state :before))
(after (vconj! state :after))]}
(expect (vconj! state :expect)))
(expect-it "tracks correctly"
(= [:before :after] @state)))
(= [:before :expect :after] @state)))
(given [state (volatile! [])]
(it "around"
{:context [(around [f]
(vswap! state conj :before)
(vconj! state :before)
(f)
(vswap! state conj :after))]}
(expect true))
(vconj! state :after))]}
(expect (vconj! state :expect)))
(expect-it "tracks correctly"
(= [:before :after] @state)))))
(= [:before :expect :after] @state)))))

(defdescribe propagate-eachs-test
(expect-it "combines correctly"
(= {:context {:before-each [1 2 3 4 5 6]
:after-each []}}
(meta (propagate-eachs {:context {:before-each [1 2 3]}}
(with-meta [] {:context {:before-each [4 5 6]}}))))))

(defdescribe complex-context-test
(given [state (volatile! [])]
(describe "top level"
{:context [(before (vconj! state :before-top))
(before-each (vconj! state :before-each-top))
(after-each (vconj! state :after-each-top))
(after (vconj! state :after-top))]}
(describe "middle level"
{:context [(before (vconj! state :before-middle))
(before-each (vconj! state :before-each-middle))
(after-each (vconj! state :after-each-middle))
(after (vconj! state :after-middle))]}
(describe "bottom level"
{:context [(before (vconj! state :before-bottom))
(before-each (vconj! state :before-each-bottom))
(after-each (vconj! state :after-each-bottom))
(after (vconj! state :after-bottom))]}
(expect-it "temp 1" (vconj! state :expect-1))
(expect-it "temp 2" (vconj! state :expect-2)))))
(expect-it "tracks correctly"
(= [:before-top
:before-middle
:before-bottom
:before-each-top
:before-each-middle
:before-each-bottom
:expect-1
:after-each-bottom
:after-each-middle
:after-each-top
:before-each-top
:before-each-middle
:before-each-bottom
:expect-2
:after-each-bottom
:after-each-middle
:after-each-top
:after-bottom
:after-middle
:after-top] @state))))

(defdescribe multiple-same-eachs-test
(given [state (volatile! [])]
(describe "top level"
{:context [(after-each (vconj! state :after-each-top))
(after-each (vconj! state :after-each-top-2))]}
(describe "middle level"
{:context [(after-each (vconj! state :after-each-middle))
(after-each (vconj! state :after-each-middle-2))]}
(describe "bottom level"
{:context [(after-each (vconj! state :after-each-bottom))
(after-each (vconj! state :after-each-bottom-2))]}
(expect-it "temp 1" (vconj! state :expect-1))
(expect-it "temp 2" (vconj! state :expect-2)))))
(expect-it "tracks correctly"
(= [:expect-1
:after-each-bottom
:after-each-bottom-2
:after-each-middle
:after-each-middle-2
:after-each-top
:after-each-top-2
:expect-2
:after-each-bottom
:after-each-bottom-2
:after-each-middle
:after-each-middle-2
:after-each-top
:after-each-top-2] @state))))
Loading

0 comments on commit 89dfb6c

Please sign in to comment.