From a2ae31c443318e23a3437dfb2c7a37f041078653 Mon Sep 17 00:00:00 2001 From: Brandon Correa <50252804+brandoncorrea@users.noreply.github.com> Date: Wed, 26 Feb 2025 12:54:15 -0600 Subject: [PATCH 1/2] Count assertions in test results --- spec/clj/speclj/report/clojure_test_spec.clj | 2 +- .../speclj/report/documentation_spec.cljc | 12 +- spec/cljc/speclj/report/progress_spec.cljc | 28 +- spec/cljc/speclj/should_spec.cljc | 241 ++++++- spec/cljc/speclj/spec_helper.cljc | 6 +- spec/cljc/speclj/stub_spec.cljc | 35 +- src/cljc/speclj/components.cljc | 3 + src/cljc/speclj/core.cljc | 591 ++++++++++-------- src/cljc/speclj/report/progress.cljc | 5 +- src/cljc/speclj/reporting.cljc | 5 +- src/cljc/speclj/results.cljc | 22 +- src/cljc/speclj/running.cljc | 68 +- 12 files changed, 633 insertions(+), 385 deletions(-) diff --git a/spec/clj/speclj/report/clojure_test_spec.clj b/spec/clj/speclj/report/clojure_test_spec.clj index d766ae6..ba48450 100644 --- a/spec/clj/speclj/report/clojure_test_spec.clj +++ b/spec/clj/speclj/report/clojure_test_spec.clj @@ -23,7 +23,7 @@ (describe "Clojure Test Reporter" (with reporter (new-clojure-test-reporter)) - (with a-failure (fail-result (new-characteristic "flips" (new-description "Crazy" false "some.ns") "flip" false) 0.3 (-new-failure "Expected flips"))) + (with a-failure (fail-result (new-characteristic "flips" (new-description "Crazy" false "some.ns") "flip" false) 0.3 (-new-failure "Expected flips") 0)) (with an-error (error-result (-new-exception "Compilation failed"))) (it "reports pass" diff --git a/spec/cljc/speclj/report/documentation_spec.cljc b/spec/cljc/speclj/report/documentation_spec.cljc index 16faf2e..62a760d 100644 --- a/spec/cljc/speclj/report/documentation_spec.cljc +++ b/spec/cljc/speclj/report/documentation_spec.cljc @@ -29,13 +29,13 @@ (it "reports pass" (let [characteristic (new-characteristic "says pass" @description "pass" false) - result (pass-result characteristic 1)] + result (pass-result characteristic 1 0)] (should= (str (green "- says pass") endl) (with-out-str (report-pass @reporter result))))) (it "reports focused pass" (let [characteristic (new-characteristic "says pass" @description #() true) - result (pass-result characteristic 1)] + result (pass-result characteristic 1 0)] (should= (str (green "- says pass") " " (yellow "[FOCUS]") endl) (with-out-str (report-pass @reporter result))))) @@ -47,13 +47,13 @@ (it "reports fail" (let [characteristic (new-characteristic "says fail" @description "fail" false) - result (fail-result characteristic 2 (-new-failure "blah"))] + result (fail-result characteristic 2 (-new-failure "blah") 0)] (should= (str (red "- says fail (FAILED)") endl) (with-out-str (report-fail @reporter result))))) (it "reports focused fail" (let [characteristic (new-characteristic "says fail" @description #() true) - result (fail-result characteristic 2 (-new-failure "blah"))] + result (fail-result characteristic 2 (-new-failure "blah") 0)] (should= (str (red "- says fail (FAILED)") " " (yellow "[FOCUS]") endl) (with-out-str (report-fail @reporter result))))) @@ -71,13 +71,13 @@ (it "reports nested pass" (let [characteristic (new-characteristic "nested pass" @nested-description "pass" false) - result (pass-result characteristic 1)] + result (pass-result characteristic 1 0)] (should= (str (green " - nested pass") endl) (with-out-str (report-pass @reporter result))))) (it "reports nested failure" (let [characteristic (new-characteristic "nested fail" @nested-description "fail" false) - result (fail-result characteristic 2 (-new-failure "blah"))] + result (fail-result characteristic 2 (-new-failure "blah") 0)] (should= (str (red " - nested fail (FAILED)") endl) (with-out-str (report-fail @reporter result))))) ) diff --git a/spec/cljc/speclj/report/progress_spec.cljc b/spec/cljc/speclj/report/progress_spec.cljc index e3dca22..8c64a24 100644 --- a/spec/cljc/speclj/report/progress_spec.cljc +++ b/spec/cljc/speclj/report/progress_spec.cljc @@ -55,9 +55,9 @@ (it "reports passing run results" (binding [*color?* true] - (let [result1 (pass-result nil 0.1) - result2 (pass-result nil 0.02) - result3 (pass-result nil 0.003) + (let [result1 (pass-result nil 0.1 1) + result2 (pass-result nil 0.02 2) + result3 (pass-result nil 0.003 3) results [result1 result2 result3] output (with-out-str (report-runs @reporter results)) lines (str/split-lines output)] @@ -65,7 +65,7 @@ (should= "" (nth lines 0)) (should= "" (nth lines 1)) (should= (str "Finished in " (platform/format-seconds 0.123) " seconds") (nth lines 2)) - (should= (green "3 examples, 0 failures") (nth lines 3))))) + (should= (green "3 examples, 0 failures, 6 assertions") (nth lines 3))))) (it "reports failing run results" (binding [*color?* true] @@ -73,9 +73,9 @@ char1 (new-characteristic "flips" description "flip" false) char2 (new-characteristic "spins" description "spin" false) char3 (new-characteristic "dives" description "dive" false) - result1 (fail-result char1 0.3 (-new-failure "Expected flips")) - result2 (fail-result char2 0.02 (-new-failure "Expected spins")) - result3 (fail-result char3 0.001 (-new-failure "Expected dives")) + result1 (fail-result char1 0.3 (-new-failure "Expected flips") 1) + result2 (fail-result char2 0.02 (-new-failure "Expected spins") 2) + result3 (fail-result char3 0.001 (-new-failure "Expected dives") 3) results [result1 result2 result3] lines (str/split-lines (with-out-str (report-runs @reporter results)))] (should= 18 (count lines)) @@ -95,18 +95,18 @@ ; (should= "/Users/micahmartin/Projects/clojure/speclj/spec/speclj/report/progress_spec.clj:56" (nth lines 14)) (should= "" (nth lines 15)) (should= (str "Finished in " (platform/format-seconds 0.321) " seconds") (nth lines 16)) - (should= (red "3 examples, 3 failures") (nth lines 17))))) + (should= (red "3 examples, 3 failures, 6 assertions") (nth lines 17))))) (it "reports pending run results" (binding [*color?* true] (let [description (new-description "Crazy" false "some.ns") char1 (new-characteristic "flips" description "flip" false) - result1 (pass-result char1 0.1) - result2 (pass-result char1 0.02) + result1 (pass-result char1 0.1 1) + result2 (pass-result char1 0.02 2) result3 (pending-result char1 0.003 (-new-pending "Blah")) results [result1 result2 result3] lines (str/split-lines (with-out-str (sut/print-summary results)))] - (should= (yellow "3 examples, 0 failures, 1 pending") (last lines))))) + (should= (yellow "3 examples, 0 failures, 3 assertions, 1 pending") (last lines))))) (it "reports pending summary" (let [description (new-description "Crazy" false "some.ns") @@ -134,12 +134,12 @@ (binding [*color?* true] (let [description (new-description "Crazy" false "some.ns") char1 (new-characteristic "flips" description "flip" false) - result1 (pass-result char1 0.1) - result2 (pass-result char1 0.02) + result1 (pass-result char1 0.1 3) + result2 (pass-result char1 0.02 2) result3 (error-result (-new-exception "blah")) results [result1 result2 result3] lines (str/split-lines (with-out-str (sut/print-summary results)))] - (should= (red "3 examples, 0 failures, 1 errors") (last lines))))) + (should= (red "3 examples, 0 failures, 5 assertions, 1 errors") (last lines))))) (it "reports error summary" (binding [*full-stack-trace?* false] diff --git a/spec/cljc/speclj/should_spec.cljc b/spec/cljc/speclj/should_spec.cljc index 92eca4d..98be28a 100644 --- a/spec/cljc/speclj/should_spec.cljc +++ b/spec/cljc/speclj/should_spec.cljc @@ -18,12 +18,14 @@ should> should>= should-fail -to-s -new-throwable -new-exception]] - [speclj.spec-helper #?(:cljs :refer-macros :default :refer) [should-fail! should-pass! failure-message]] + [speclj.components :as components] + [speclj.spec-helper #?(:cljs :refer-macros :default :refer) [should-fail! should-pass! failure-message should-have-assertions]] [speclj.platform :refer [endl exception type-name throwable]] [speclj.run.standard :as standard] [clojure.string :as str])) (describe "Should Assertions: " + (context "should" (it "tests truthy" (should-pass! (should true)) @@ -31,7 +33,12 @@ (it "failure message is nice" (should= "Expected truthy but was: false" (failure-message (should false))) - (should= "Expected truthy but was: nil" (failure-message (should nil))))) + (should= "Expected truthy but was: nil" (failure-message (should nil)))) + + (it "bumps assertion count" + (should true) + (should-have-assertions 1)) + ) (context "should-not" (it "tests falsy" @@ -40,7 +47,12 @@ (it "failure message is nice" (should= "Expected falsy but was: true" (failure-message (should-not true))) - (should= "Expected falsy but was: 1" (failure-message (should-not 1))))) + (should= "Expected falsy but was: 1" (failure-message (should-not 1)))) + + (it "bumps assertion count" + (should-not false) + (should-have-assertions 1)) + ) (context "should=" (it "tests equality" @@ -69,21 +81,38 @@ (it "prints lazy seqs nicely" (should= (str "Expected: (1 2 3)" endl " got: (3 2 1) (using =)") - (failure-message (should= '(1 2 3) (concat '(3) '(2 1))))))) + (failure-message (should= '(1 2 3) (concat '(3) '(2 1)))))) + + (it "bumps assertion count" + (should= 1 1.01 0.1) + (should= 1 1) + (should-have-assertions 2)) + ) (context "should-be" (it "tests functionality" (should-pass! (should-be empty? [])) (should-fail! (should-be empty? [1 2 3])) (should= "Expected [1 2 3] to satisfy: empty?" (failure-message (should-be empty? [1 2 (+ 1 2)]))) - (should= "Expected [1 2 3] to satisfy: (comp zero? first)" (failure-message (should-be (comp zero? first) [1 2 (+ 1 2)]))))) + (should= "Expected [1 2 3] to satisfy: (comp zero? first)" (failure-message (should-be (comp zero? first) [1 2 (+ 1 2)])))) + + (it "bumps assertion count" + (should-be empty? []) + (should-be empty? []) + (should-have-assertions 2)) + ) (context "should-not-be" (it "tests complementary functionality" (should-pass! (should-not-be empty? [1 2 3])) (should-fail! (should-not-be empty? [])) (should= "Expected 1 not to satisfy: pos?" (failure-message (should-not-be pos? 1))) - (should= "Expected 1 not to satisfy: (comp pos? inc)" (failure-message (should-not-be (comp pos? inc) 1))))) + (should= "Expected 1 not to satisfy: (comp pos? inc)" (failure-message (should-not-be (comp pos? inc) 1)))) + + (it "bumps assertion count" + (should-not-be empty? [1 2 3]) + (should-have-assertions 1)) + ) (context "should-not=" (it "tests inequality" @@ -93,7 +122,13 @@ (it "failure message is nice" (should= (str "Expected: 1" endl "not to =: 1") - (failure-message (should-not= 1 1))))) + (failure-message (should-not= 1 1)))) + + (it "bumps assertion count" + (should-not= 1 2) + (should-not= 1 2) + (should-have-assertions 2)) + ) (context "should-be-same" (it "tests identity" @@ -106,7 +141,13 @@ (it "failure message is nice" (should= (str " Expected: 1" endl "to be the same as: 2 (using identical?)") - (failure-message (should-be-same 1 2))))) + (failure-message (should-be-same 1 2)))) + + (it "bumps assertion count" + (should-be-same 1 1) + (should-be-same 1 1) + (should-have-assertions 2)) + ) (context "should-not-be-same" (it "tests identity" @@ -120,16 +161,33 @@ (it "failure message is nice" (should= (str " Expected: 1" endl "not to be the same as: 1 (using identical?)") - (failure-message (should-not-be-same 1 1))))) + (failure-message (should-not-be-same 1 1)))) + + (it "bumps assertion count" + (should-not-be-same [] ()) + (should-not-be-same [] ()) + (should-have-assertions 2)) + ) (context "should-be-nil" (it "checks for equality with nil" (should-pass! (should-be-nil nil)) (should-fail! (should-be-nil true)) - (should-fail! (should-be-nil false)))) + (should-fail! (should-be-nil false))) + + (it "bumps assertion count" + (should-be-nil nil) + (should-be-nil nil) + (should-have-assertions 2)) + ) (context "should==" + (it "bumps assertion count" + (should== 1 1) + (should== 1 1) + (should-have-assertions 2)) + (context "numbers" (it "tests loose equality" (should-pass! (should== 1 1)) @@ -217,10 +275,16 @@ ; (failure-message (should== {:a 1} {:a 1 :b 2}))) ) - )) + ) + ) (context "should-not==" + (it "bumps assertion count" + (should-not== 1 2) + (should-not== 1 2) + (should-have-assertions 2)) + (context "numbers" (it "tests loose equality" (should-fail! (should-not== 1 1)) @@ -309,7 +373,13 @@ (it "handles nil containers gracefully" (should-fail! (should-contain "foo" nil)) - (should-fail! (should-contain nil nil)))) + (should-fail! (should-contain nil nil))) + + (it "bumps assertion count" + (should-contain "foo" "foobar") + (should-contain "foo" "foobar") + (should-have-assertions 2)) + ) (context "should-not-contain" (it "checks for non-membership of precise strings" @@ -344,7 +414,13 @@ (it "handles nil containers gracefully" (should-pass! (should-not-contain "foo" nil)) - (should-pass! (should-not-contain nil nil)))) + (should-pass! (should-not-contain nil nil))) + + (it "bumps assertion count" + (should-not-contain "foo" "bar") + (should-not-contain "foo" "bar") + (should-have-assertions 2)) + ) (context "should-have-count" (it "checks for an exact count" @@ -370,7 +446,12 @@ (should-throw exception (str "should-have-count doesn't know how to handle these types: [" (type-name (type 1)) " " (type-name (type :not-countable)) "]") (should-have-count 1 :not-countable)) (should-throw exception (str "should-have-count doesn't know how to handle these types: [" (type-name (type :nan)) " " (type-name (type [])) "]") - (should-have-count :nan [])))) + (should-have-count :nan []))) + + (it "bumps assertion count" + (should-have-count 0 nil) + (should-have-count 0 []) + (should-have-assertions 2))) (context "should-not-have-count" (it "checks for anything but an exact count" @@ -394,13 +475,25 @@ (should-throw exception (str "should-not-have-count doesn't know how to handle these types: [" (type-name (type 1)) " " (type-name (type :not-countable)) "]") (should-not-have-count 1 :not-countable)) (should-throw exception (str "should-not-have-count doesn't know how to handle these types: [" (type-name (type :nan)) " " (type-name (type [])) "]") - (should-not-have-count :nan [])))) + (should-not-have-count :nan []))) + + (it "bumps assertion count" + (should-not-have-count 1 nil) + (should-not-have-count 1 []) + (should-have-assertions 2)) + ) (context "should-not-be-nil" (it "checks for inequality with nil" (should-fail! (should-not-be-nil nil)) (should-pass! (should-not-be-nil true)) - (should-pass! (should-not-be-nil false)))) + (should-pass! (should-not-be-nil false))) + + (it "bumps assertion count" + (should-not-be-nil false) + (should-not-be-nil true) + (should-have-assertions 2)) + ) (context "should-fail" (it "is an automatic failure" @@ -409,7 +502,14 @@ (it "can take a string as the error message" (should-fail! (should-fail "some message")) - (should= "some message" (failure-message (should-fail "some message"))))) + (should= "some message" (failure-message (should-fail "some message")))) + + (it "bumps assertion count" + (should-fail! (should-fail)) + (should-fail! (should-fail)) + (should-fail! (should-fail)) + (should-have-assertions 3)) + ) (context "should-start-with" (it "checks for prefix in strings" @@ -429,7 +529,13 @@ (it "errors on unexpected types" (should-throw exception (str "should-start-with doesn't know how to handle these types: [" (type-name (type 1)) " " (type-name (type 1)) "]") - (should-start-with 1 2)))) + (should-start-with 1 2))) + + (it "bumps assertion count" + (should-start-with "abc" "abcdefg") + (should-start-with "abc" "abcdefg") + (should-have-assertions 2)) + ) (context "should-not-start-with" (it "checks for prefix in strings" @@ -448,7 +554,13 @@ (it "errors on unexpected types" (should-throw exception (str "should-not-start-with doesn't know how to handle these types: [" (type-name (type 1)) " " (type-name (type 1)) "]") - (should-not-start-with 1 2)))) + (should-not-start-with 1 2))) + + (it "bumps assertion count" + (should-not-start-with "bcd" "abcdefg") + (should-not-start-with "bcd" "abcdefg") + (should-have-assertions 2)) + ) (context "should-end-with" (it "checks for prefix in strings" @@ -468,7 +580,13 @@ (it "errors on unexpected types" (should-throw exception (str "should-end-with doesn't know how to handle these types: [" (type-name (type 1)) " " (type-name (type 1)) "]") - (should-end-with 1 2)))) + (should-end-with 1 2))) + + (it "bumps assertion count" + (should-pass! (should-end-with "xyz" "tuvwxyz")) + (should-pass! (should-end-with "xyz" "tuvwxyz")) + (should-have-assertions 2)) + ) (context "should-not-end-with" (it "checks for prefix in strings" @@ -487,7 +605,13 @@ (it "errors on unexpected types" (should-throw exception (str "should-not-end-with doesn't know how to handle these types: [" (type-name (type 1)) " " (type-name (type 1)) "]") - (should-not-end-with 1 2)))) + (should-not-end-with 1 2))) + + (it "bumps assertion count" + (should-not-end-with "wxy" "tuvwxyz") + (should-not-end-with "wxy" "tuvwxyz") + (should-have-assertions 2)) + ) (context "should-throw" @@ -554,9 +678,27 @@ (should-pass! (should-throw exception #(empty? (speclj.platform/error-message %)) (throw (-new-exception "")))) (should-fail! (should-throw exception #((not (empty? (speclj.platform/error-message %))) (throw (-new-exception ""))))) (should= (str "Expected exception predicate didn't match" endl "Expected: true" endl " got: \"Not my message\" (using =)") - (failure-message (should-throw exception #(speclj.platform/error-message %) (throw (-new-exception "Not my message"))))))) + (failure-message (should-throw exception #(speclj.platform/error-message %) (throw (-new-exception "Not my message")))))) + + (it "bumps assertions once for form" + (should-throw (throw (ex-info "" {}))) + (should-have-assertions 1)) + + (it "bumps assertions once for form and type" + (should-throw #?(:cljs js/Error :default clojure.lang.ExceptionInfo) (throw (ex-info "" {}))) + (should-have-assertions 1)) + + (it "bumps assertions twice for form and passing predicate" + (should-throw #?(:cljs js/Error :default clojure.lang.ExceptionInfo) "" (throw (ex-info "" {}))) + (should-have-assertions 2)) + + (it "bumps assertions twice for form and failing predicate" + (should-fail! (should-throw #?(:cljs js/Error :default clojure.lang.ExceptionInfo) "blah" (throw (ex-info "" {})))) + (should-have-assertions 2)) + ) (context "should-not-throw" + (it "tests that nothing was thrown" (should-pass! (should-not-throw (+ 1 1))) (should-fail! (should-not-throw (throw (-new-throwable "error")))) @@ -570,8 +712,13 @@ (str "Expected nothing thrown from: " (pr-str '(throw (-new-throwable "error"))) endl (str " but got: #error {\n :cause \"error\"\n :via\n [{:type " (type-name throwable) "\n :message \"error\"\n")) - (failure-message (should-not-throw (throw (-new-throwable "error")))))))) + (failure-message (should-not-throw (throw (-new-throwable "error"))))))) + (it "bumps assertion count" + (should-not-throw (+ 1 1)) + (should-not-throw (+ 2 2)) + (should-have-assertions 2)) + ) (context "should-be-a" (it "passes if the actual form is an instance of the expected type" @@ -588,7 +735,13 @@ (it "fails with an error message" (should= (str "Expected 1 to be an instance of: " (-to-s (type :foo)) endl " but was an instance of: " (-to-s (type 1)) " (using isa?)") - (failure-message (should-be-a (type :foo) 1))))) + (failure-message (should-be-a (type :foo) 1)))) + + (it "bumps assertion count" + (should-be-a (type 1) 1) + (should-be-a (type 2) 2) + (should-have-assertions 2)) + ) (context "should-not-be-a" @@ -606,7 +759,14 @@ (it "fails with an error message" (should= (str "Expected :bar not to be an instance of " (-to-s (type :bar)) " but was (using isa?)") - (failure-message (should-not-be-a (type :foo) :bar))))) + (failure-message (should-not-be-a (type :foo) :bar)))) + + (it "bumps assertion count" + (should-not-be-a (type "") 1) + (should-not-be-a (type "") 1) + (should-not-be-a (type "") 1) + (should-have-assertions 3)) + ) (context "should<" (it "degenerate cases" @@ -622,7 +782,13 @@ (it "passing cases" (should-pass! (should< 1 2)) - (should-pass! (should< 1.0 1.000001)))) + (should-pass! (should< 1.0 1.000001))) + + (it "bumps assertion count" + (should< 1 2) + (should< 1 2) + (should-have-assertions 2)) + ) (context "should>" (it "degenerate cases" @@ -638,7 +804,13 @@ (it "passing cases" (should-pass! (should> 2 1)) - (should-pass! (should> 1.000001 1.0)))) + (should-pass! (should> 1.000001 1.0))) + + (it "bumps assertion count" + (should> 2 1) + (should> 2 1) + (should-have-assertions 2)) + ) (context "should<=" (it "degenerate cases" @@ -655,7 +827,13 @@ (should-pass! (should<= 1 1)) (should-pass! (should<= 1 1.0)) (should-pass! (should<= 1 2)) - (should-pass! (should<= 1.0 1.000001)))) + (should-pass! (should<= 1.0 1.000001))) + + (it "bumps assertion count" + (should<= 1 2) + (should<= 1 2) + (should-have-assertions 2)) + ) (context "should>=" (it "degenerate cases" @@ -672,7 +850,12 @@ (should-pass! (should>= 1 1)) (should-pass! (should>= 1 1.0)) (should-pass! (should>= 2 1)) - (should-pass! (should>= 1.000001 1.0)))) + (should-pass! (should>= 1.000001 1.0))) + + (it "bumps assertion count" + (should>= 2 1) + (should>= 2 1) + (should-have-assertions 2))) ) diff --git a/spec/cljc/speclj/spec_helper.cljc b/spec/cljc/speclj/spec_helper.cljc index 23f51cd..5c5bf52 100644 --- a/spec/cljc/speclj/spec_helper.cljc +++ b/spec/cljc/speclj/spec_helper.cljc @@ -12,9 +12,13 @@ (defmacro should-pass! [& body] `(let [result# (run-result ~@body)] - (if (not (= :pass result#)) + (when-not (= :pass result#) (-fail (str "Unexpected failure: " (speclj.platform/error-message result#)))))) +(defmacro should-have-assertions [n] + `(let [assertions# @components/*assertions*] + (should= ~n assertions#))) + (defn to-string [v] (#?(:cljr .ToString :default .toString) v)) (defmacro should-fail! [& body] diff --git a/spec/cljc/speclj/stub_spec.cljc b/spec/cljc/speclj/stub_spec.cljc index 2aaaabd..15c7ae5 100644 --- a/spec/cljc/speclj/stub_spec.cljc +++ b/spec/cljc/speclj/stub_spec.cljc @@ -1,5 +1,5 @@ (ns speclj.stub-spec - (:require [speclj.spec-helper #?(:cljs :refer-macros :default :refer) [should-fail! should-pass! failure-message]] + (:require [speclj.spec-helper #?(:cljs :refer-macros :default :refer) [should-fail! should-pass! failure-message should-have-assertions]] [speclj.core #?(:cljs :refer-macros :default :refer) [around before context describe it should= should-throw should-invoke should-have-invoked should-not-invoke should-not-have-invoked with with-stubs stub -new-exception]] [speclj.stub :as stub] [speclj.platform :refer [endl exception]] @@ -206,6 +206,12 @@ (failure-message (should-have-invoked :the-stub {:with [:one :two] :times 2}))) (should= (str "Expected: 1 invocation of :the-stub with [:three :four]" endl " got: 2 invocations") (failure-message (should-have-invoked :the-stub {:with [:three :four] :times 1}))))) + + (it "bumps assertion count" + (let [f (stub :the-stub)] + (f) + (should-have-invoked :the-stub) + (should-have-assertions 1))) ) (context "should-not-have-invoked" @@ -269,6 +275,9 @@ (should= (str "Expected: :the-stub not to have been invoked with [:three :four]" endl " got: ([:one :two] [:three :four] [:three :four])") (failure-message (should-not-have-invoked :the-stub {:with [:three :four]}))))) + (it "bumps assertion count" + (should-not-have-invoked :the-stub) + (should-have-assertions 1)) ) (context "should-invoke" @@ -296,6 +305,20 @@ (should-invoke reverse {:return 42 :times 2} (should= 42 (reverse [1 2])))))) + (it "allows for nested should-invoke's" + (should-pass! + (should-invoke reverse {:times 1} + (should-invoke println {:times 1} + (println "hello!") + (reverse [1 2]))))) + + (it "bumps assertion count" + (should-invoke println {} (println "Hello!")) + (should-have-assertions 1)) + ) + + (context "should-not-invoke" + (it "stubs and checks it was not called - failing" (should-fail! (should-not-invoke println {} (println "Hello!"))) (should= "Expected: 0 invocations of println\n got: 1" @@ -309,13 +332,6 @@ (println "hello!") (println "hello again!")))) - (it "allows for nested should-invoke's" - (should-pass! - (should-invoke reverse {:times 1} - (should-invoke println {:times 1} - (println "hello!") - (reverse [1 2]))))) - (it "allows for nested should-not-invoke's" (should-pass! (should-invoke reverse {:times 2} @@ -324,6 +340,9 @@ (println "hello!") (reverse [1 2]))))) + (it "bumps assertion count" + (should-not-invoke println {} "No calls to println :(") + (should-have-assertions 1)) ) ) ) diff --git a/src/cljc/speclj/components.cljc b/src/cljc/speclj/components.cljc index d1b2c8c..8633759 100644 --- a/src/cljc/speclj/components.cljc +++ b/src/cljc/speclj/components.cljc @@ -43,6 +43,9 @@ (defn is-description? [component] (instance? Description component)) +(declare ^:dynamic *assertions*) +(defn inc-assertions! [] (swap! *assertions* inc)) + (deftype Characteristic [name parent body is-focused?] SpecComponent (install [this description] diff --git a/src/cljc/speclj/core.cljc b/src/cljc/speclj/core.cljc index 73aacbb..f39bd7e 100644 --- a/src/cljc/speclj/core.cljc +++ b/src/cljc/speclj/core.cljc @@ -60,6 +60,10 @@ (speclj.running/submit-description (speclj.config/active-runner) description#)) description#)) +(defmacro ^:no-doc help-should [& body] + `(do (speclj.components/inc-assertions!) + ~@body)) + (defmacro it "body => any forms, but should contain at least one assertion (should) @@ -227,61 +231,72 @@ (defmacro should "Asserts the truthy-ness of a form" [form] - `(let [value# ~form] - (when-not value# - (-fail (str "Expected truthy but was: " (-to-s value#) ""))))) + `(help-should + (let [value# ~form] + (when-not value# + (-fail (str "Expected truthy but was: " (-to-s value#) "")))))) (defmacro should-not "Asserts the falsy-ness of a form" [form] - `(when-let [value# ~form] - (-fail (str "Expected falsy but was: " (-to-s value#))))) + `(help-should + (when-let [value# ~form] + (-fail (str "Expected falsy but was: " (-to-s value#)))))) (defmacro should= "Asserts that two forms evaluate to equal values, with the expected value as the first parameter." ([expected-form actual-form] - `(let [expected# ~expected-form actual# ~actual-form] - (when-not (= expected# actual#) - (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl " got: " (-to-s actual#) " (using =)"))))) + `(help-should + (let [expected# ~expected-form actual# ~actual-form] + (when-not (= expected# actual#) + (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl " got: " (-to-s actual#) " (using =)")))))) ([expected-form actual-form delta-form] - `(let [expected# ~expected-form actual# ~actual-form delta# ~delta-form] - (when (speclj.platform/difference-greater-than-delta? expected# actual# delta#) - (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl " got: " (-to-s actual#) " (using delta: " delta# ")")))))) + `(help-should + (let [expected# ~expected-form + actual# ~actual-form + delta# ~delta-form] + (when (speclj.platform/difference-greater-than-delta? expected# actual# delta#) + (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl " got: " (-to-s actual#) " (using delta: " delta# ")"))))))) (defmacro should-be "Asserts that a form satisfies a function." [f-form actual-form] - `(let [f# ~f-form actual# ~actual-form] - (when-not (f# actual#) - (-fail (str "Expected " (-to-s actual#) " to satisfy: " ~(str f-form)))))) + `(help-should + (let [f# ~f-form actual# ~actual-form] + (when-not (f# actual#) + (-fail (str "Expected " (-to-s actual#) " to satisfy: " ~(str f-form))))))) (defmacro should-not-be "Asserts that a form does not satisfy a function." [f-form actual-form] - `(let [f# ~f-form actual# ~actual-form] - (when (f# actual#) - (-fail (str "Expected " (-to-s actual#) " not to satisfy: " ~(str f-form)))))) + `(help-should + (let [f# ~f-form actual# ~actual-form] + (when (f# actual#) + (-fail (str "Expected " (-to-s actual#) " not to satisfy: " ~(str f-form))))))) (defmacro should-not= "Asserts that two forms evaluate to unequal values, with the unexpected value as the first parameter." [expected-form actual-form] - `(let [expected# ~expected-form actual# ~actual-form] - (when (= expected# actual#) - (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "not to =: " (-to-s actual#)))))) + `(help-should + (let [expected# ~expected-form actual# ~actual-form] + (when (= expected# actual#) + (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "not to =: " (-to-s actual#))))))) (defmacro should-be-same "Asserts that two forms evaluate to the same object, with the expected value as the first parameter." [expected-form actual-form] - `(let [expected# ~expected-form actual# ~actual-form] - (if (not (identical? expected# actual#)) - (-fail (str " Expected: " (-to-s expected#) speclj.platform/endl "to be the same as: " (-to-s actual#) " (using identical?)"))))) + `(help-should + (let [expected# ~expected-form actual# ~actual-form] + (if (not (identical? expected# actual#)) + (-fail (str " Expected: " (-to-s expected#) speclj.platform/endl "to be the same as: " (-to-s actual#) " (using identical?)")))))) (defmacro should-not-be-same "Asserts that two forms evaluate to different objects, with the unexpected value as the first parameter." [expected-form actual-form] - `(let [expected# ~expected-form actual# ~actual-form] - (when (identical? expected# actual#) - (-fail (str " Expected: " (-to-s expected#) speclj.platform/endl "not to be the same as: " (-to-s actual#) " (using identical?)"))))) + `(help-should + (let [expected# ~expected-form actual# ~actual-form] + (when (identical? expected# actual#) + (-fail (str " Expected: " (-to-s expected#) speclj.platform/endl "not to be the same as: " (-to-s actual#) " (using identical?)")))))) (defmacro should-be-nil "Asserts that the form evaluates to nil" @@ -296,44 +311,46 @@ (should-contain :foo {:foo :bar}) ; looks for a key in a map (should-contain 3 [1 2 3 4]) ; looks for an object in a collection" [expected actual] - `(let [expected# ~expected - actual# ~actual] - (cond - (nil? actual#) (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "to be in: nil")) - (and (string? expected#) (string? actual#)) - (when (nil? (clojure.string/index-of actual# expected#)) - (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "to be in: " (-to-s actual#) " (using .contains)"))) - (and (speclj.platform/re? expected#) (string? actual#)) - (when (empty? (re-seq expected# actual#)) - (-fail (str "Expected: " (-to-s actual#) speclj.platform/endl "to match: " (-to-s expected#) " (using re-seq)"))) - (map? actual#) - (when-not (contains? actual# expected#) - (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "to be a key in: " (-to-s actual#) " (using contains?)"))) - (coll? actual#) - (when-not (some #(= expected# %) actual#) - (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "to be in: " (-to-s actual#) " (using =)"))) - :else (throw (-new-exception (wrong-types "should-contain" expected# actual#)))))) + `(help-should + (let [expected# ~expected + actual# ~actual] + (cond + (nil? actual#) (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "to be in: nil")) + (and (string? expected#) (string? actual#)) + (when (nil? (clojure.string/index-of actual# expected#)) + (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "to be in: " (-to-s actual#) " (using .contains)"))) + (and (speclj.platform/re? expected#) (string? actual#)) + (when (empty? (re-seq expected# actual#)) + (-fail (str "Expected: " (-to-s actual#) speclj.platform/endl "to match: " (-to-s expected#) " (using re-seq)"))) + (map? actual#) + (when-not (contains? actual# expected#) + (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "to be a key in: " (-to-s actual#) " (using contains?)"))) + (coll? actual#) + (when-not (some #(= expected# %) actual#) + (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "to be in: " (-to-s actual#) " (using =)"))) + :else (throw (-new-exception (wrong-types "should-contain" expected# actual#))))))) (defmacro should-not-contain "Multipurpose assertion of non-containment. See should-contain as an example of opposite behavior." [expected actual] - `(let [expected# ~expected - actual# ~actual] - (cond - (nil? actual#) nil ; automatic pass! - (and (string? expected#) (string? actual#)) - (when (some? (clojure.string/index-of actual# expected#)) - (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "not to be in: " (-to-s actual#) " (using .contains)"))) - (and (speclj.platform/re? expected#) (string? actual#)) - (when (seq (re-seq expected# actual#)) - (-fail (str "Expected: " (-to-s actual#) speclj.platform/endl "not to match: " (-to-s expected#) " (using re-seq)"))) - (map? actual#) - (when (contains? actual# expected#) - (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "not to be a key in: " (-to-s actual#) " (using contains?)"))) - (coll? actual#) - (when (some #(= expected# %) actual#) - (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "not to be in: " (-to-s actual#) " (using =)"))) - :else (throw (-new-exception (wrong-types "should-not-contain" expected# actual#)))))) + `(help-should + (let [expected# ~expected + actual# ~actual] + (cond + (nil? actual#) nil ; automatic pass! + (and (string? expected#) (string? actual#)) + (when (some? (clojure.string/index-of actual# expected#)) + (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "not to be in: " (-to-s actual#) " (using .contains)"))) + (and (speclj.platform/re? expected#) (string? actual#)) + (when (seq (re-seq expected# actual#)) + (-fail (str "Expected: " (-to-s actual#) speclj.platform/endl "not to match: " (-to-s expected#) " (using re-seq)"))) + (map? actual#) + (when (contains? actual# expected#) + (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "not to be a key in: " (-to-s actual#) " (using contains?)"))) + (coll? actual#) + (when (some #(= expected# %) actual#) + (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl "not to be in: " (-to-s actual#) " (using =)"))) + :else (throw (-new-exception (wrong-types "should-not-contain" expected# actual#))))))) (defmacro should-have-count "Multipurpose assertion on (count %). Works on strings, sequences, and maps. @@ -344,15 +361,16 @@ (should-have-count 0 []) (should-have-count 0 nil)" [expected coll] - `(let [expected# ~expected - coll# ~coll] - (if-not (and (number? expected#) (or (nil? coll#) (string? coll#) (counted? coll#))) - (throw (-new-exception (wrong-types "should-have-count" expected# coll#))) - (let [actual# (count coll#)] - (when-not (= expected# actual#) - (-fail (str "Expected count: " expected# speclj.platform/endl - "Actual count: " actual# speclj.platform/endl - "Actual coll: " (-to-s coll#)))))))) + `(help-should + (let [expected# ~expected + coll# ~coll] + (if-not (and (number? expected#) (or (nil? coll#) (string? coll#) (counted? coll#))) + (throw (-new-exception (wrong-types "should-have-count" expected# coll#))) + (let [actual# (count coll#)] + (when-not (= expected# actual#) + (-fail (str "Expected count: " expected# speclj.platform/endl + "Actual count: " actual# speclj.platform/endl + "Actual coll: " (-to-s coll#))))))))) (defmacro should-not-have-count "Multipurpose assertion on (not= (count %)). Works on strings, sequences, and maps. @@ -363,14 +381,15 @@ (should-not-have-count 1 []) (should-not-have-count 1 nil)" [expected coll] - `(let [expected# ~expected - coll# ~coll] - (if-not (and (number? expected#) (or (nil? coll#) (string? coll#) (counted? coll#))) - (throw (-new-exception (wrong-types "should-not-have-count" expected# coll#))) - (let [actual# (count coll#)] - (when (= expected# actual#) - (-fail (str "Expected count to not equal " expected# " (but it did!)" speclj.platform/endl - "Collection: " (-to-s coll#)))))))) + `(help-should + (let [expected# ~expected + coll# ~coll] + (if-not (and (number? expected#) (or (nil? coll#) (string? coll#) (counted? coll#))) + (throw (-new-exception (wrong-types "should-not-have-count" expected# coll#))) + (let [actual# (count coll#)] + (when (= expected# actual#) + (-fail (str "Expected count to not equal " expected# " (but it did!)" speclj.platform/endl + "Collection: " (-to-s coll#))))))))) (defmacro ^:no-doc -remove-first [coll value] `(let [value# ~value] @@ -402,45 +421,47 @@ (should-start-with \"foo\" \"foobar\") ; looks for string prefix (should-start-with [1 2] [1 2 3 4]) ; looks for a subset at start of collection" [prefix whole] - `(let [prefix# ~prefix - whole# ~whole] - (cond - (and (string? prefix#) (string? whole#)) - (when-not (clojure.string/starts-with? whole# prefix#) - (-fail (str "Expected \"" whole# "\" to start" speclj.platform/endl - " with \"" prefix# "\""))) - - (and (coll? whole#) (coll? prefix#)) - (let [actual# (take (count prefix#) whole#) - extra# (-coll-difference actual# prefix#) - missing# (-coll-difference prefix# actual#)] - (when-not (and (empty? extra#) (empty? missing#)) - (-fail (str "Expected " (-to-s whole#) " to start" speclj.platform/endl - " with " (-to-s prefix#))))) - - :else - (throw (-new-exception (wrong-types "should-start-with" prefix# whole#)))))) + `(help-should + (let [prefix# ~prefix + whole# ~whole] + (cond + (and (string? prefix#) (string? whole#)) + (when-not (clojure.string/starts-with? whole# prefix#) + (-fail (str "Expected \"" whole# "\" to start" speclj.platform/endl + " with \"" prefix# "\""))) + + (and (coll? whole#) (coll? prefix#)) + (let [actual# (take (count prefix#) whole#) + extra# (-coll-difference actual# prefix#) + missing# (-coll-difference prefix# actual#)] + (when-not (and (empty? extra#) (empty? missing#)) + (-fail (str "Expected " (-to-s whole#) " to start" speclj.platform/endl + " with " (-to-s prefix#))))) + + :else + (throw (-new-exception (wrong-types "should-start-with" prefix# whole#))))))) (defmacro should-not-start-with "The inverse of should-start-with." [prefix whole] - `(let [prefix# ~prefix - whole# ~whole] - (cond - (and (string? prefix#) (string? whole#)) - (when (clojure.string/starts-with? whole# prefix#) - (-fail (str "Expected \"" whole# "\" to NOT start" speclj.platform/endl - " with \"" prefix# "\""))) - - (and (coll? whole#) (coll? prefix#)) - (let [actual# (take (count prefix#) whole#) - extra# (-coll-difference actual# prefix#) - missing# (-coll-difference prefix# actual#)] - (when (and (empty? extra#) (empty? missing#)) - (-fail (str "Expected " (-to-s whole#) " to NOT start" speclj.platform/endl - " with " (-to-s prefix#))))) - - :else (throw (-new-exception (wrong-types "should-not-start-with" prefix# whole#)))))) + `(help-should + (let [prefix# ~prefix + whole# ~whole] + (cond + (and (string? prefix#) (string? whole#)) + (when (clojure.string/starts-with? whole# prefix#) + (-fail (str "Expected \"" whole# "\" to NOT start" speclj.platform/endl + " with \"" prefix# "\""))) + + (and (coll? whole#) (coll? prefix#)) + (let [actual# (take (count prefix#) whole#) + extra# (-coll-difference actual# prefix#) + missing# (-coll-difference prefix# actual#)] + (when (and (empty? extra#) (empty? missing#)) + (-fail (str "Expected " (-to-s whole#) " to NOT start" speclj.platform/endl + " with " (-to-s prefix#))))) + + :else (throw (-new-exception (wrong-types "should-not-start-with" prefix# whole#))))))) (defmacro should-end-with "Assertion of suffix in strings and sequences. @@ -448,53 +469,55 @@ (should-end-with \"foo\" \"foobar\") ; looks for string suffix (should-end-with [1 2] [1 2 3 4]) ; looks for a subset at end of collection" [suffix whole] - `(let [suffix# ~suffix - whole# ~whole] - (cond - (and (string? suffix#) (string? whole#)) - (when-not (clojure.string/ends-with? whole# suffix#) - (let [padding# (apply str (repeat (- (count whole#) (count suffix#)) " "))] - (-fail (str "Expected [" whole# "] to end\n" padding# - " with [" suffix# "]")))) - - (and (coll? whole#) (coll? suffix#)) - (let [actual# (take-last (count suffix#) whole#) - extra# (-coll-difference actual# suffix#) - missing# (-coll-difference suffix# actual#)] - (when-not (and (empty? extra#) (empty? missing#)) - (let [whole# (-to-s whole#) - suffix# (-to-s suffix#) - padding# (apply str (repeat (- (count whole#) (count suffix#)) " "))] - (-fail (str "Expected " whole# " to end\n" padding# - " with " suffix#))))) - - :else - (throw (-new-exception (wrong-types "should-end-with" suffix# whole#)))))) + `(help-should + (let [suffix# ~suffix + whole# ~whole] + (cond + (and (string? suffix#) (string? whole#)) + (when-not (clojure.string/ends-with? whole# suffix#) + (let [padding# (apply str (repeat (- (count whole#) (count suffix#)) " "))] + (-fail (str "Expected [" whole# "] to end\n" padding# + " with [" suffix# "]")))) + + (and (coll? whole#) (coll? suffix#)) + (let [actual# (take-last (count suffix#) whole#) + extra# (-coll-difference actual# suffix#) + missing# (-coll-difference suffix# actual#)] + (when-not (and (empty? extra#) (empty? missing#)) + (let [whole# (-to-s whole#) + suffix# (-to-s suffix#) + padding# (apply str (repeat (- (count whole#) (count suffix#)) " "))] + (-fail (str "Expected " whole# " to end\n" padding# + " with " suffix#))))) + + :else + (throw (-new-exception (wrong-types "should-end-with" suffix# whole#))))))) (defmacro should-not-end-with "The inverse of should-end-with." [prefix whole] - `(let [prefix# ~prefix - whole# ~whole] - (cond - (and (string? prefix#) (string? whole#)) - (when (clojure.string/ends-with? whole# prefix#) - (let [padding# (apply str (repeat (- (count whole#) (count prefix#)) " "))] - (-fail (str "Expected [" whole# "] to NOT end\n" padding# - " with [" prefix# "]")))) - - (and (coll? whole#) (coll? prefix#)) - (let [actual# (take-last (count prefix#) whole#) - extra# (-coll-difference actual# prefix#) - missing# (-coll-difference prefix# actual#)] - (when (and (empty? extra#) (empty? missing#)) - (let [whole# (-to-s whole#) - prefix# (-to-s prefix#) - padding# (apply str (repeat (- (count whole#) (count prefix#)) " "))] - (-fail (str "Expected " whole# " to NOT end\n" padding# - " with " prefix#))))) - - :else (throw (-new-exception (wrong-types "should-not-end-with" prefix# whole#)))))) + `(help-should + (let [prefix# ~prefix + whole# ~whole] + (cond + (and (string? prefix#) (string? whole#)) + (when (clojure.string/ends-with? whole# prefix#) + (let [padding# (apply str (repeat (- (count whole#) (count prefix#)) " "))] + (-fail (str "Expected [" whole# "] to NOT end\n" padding# + " with [" prefix# "]")))) + + (and (coll? whole#) (coll? prefix#)) + (let [actual# (take-last (count prefix#) whole#) + extra# (-coll-difference actual# prefix#) + missing# (-coll-difference prefix# actual#)] + (when (and (empty? extra#) (empty? missing#)) + (let [whole# (-to-s whole#) + prefix# (-to-s prefix#) + padding# (apply str (repeat (- (count whole#) (count prefix#)) " "))] + (-fail (str "Expected " whole# " to NOT end\n" padding# + " with " prefix#))))) + + :else (throw (-new-exception (wrong-types "should-not-end-with" prefix# whole#))))))) (defmacro ^:no-doc -difference-message [expected actual extra missing] `(str @@ -508,36 +531,38 @@ When passed collections it will check that they have the same contents. For anything else it will assert that clojure.core/== returns true." [expected actual] - `(let [expected# ~expected - actual# ~actual] - (cond - (and (coll? expected#) (coll? actual#)) - (let [extra# (-coll-difference actual# expected#) - missing# (-coll-difference expected# actual#)] - (when-not (and (empty? extra#) (empty? missing#)) - (-fail (-difference-message expected# actual# extra# missing#)))) - (and (number? expected#) (number? actual#)) - (when-not (== expected# actual#) - (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl " got: " (-to-s actual#) " (using ==)"))) - :else (throw (-new-exception (wrong-types "should==" expected# actual#)))))) + `(help-should + (let [expected# ~expected + actual# ~actual] + (cond + (and (coll? expected#) (coll? actual#)) + (let [extra# (-coll-difference actual# expected#) + missing# (-coll-difference expected# actual#)] + (when-not (and (empty? extra#) (empty? missing#)) + (-fail (-difference-message expected# actual# extra# missing#)))) + (and (number? expected#) (number? actual#)) + (when-not (== expected# actual#) + (-fail (str "Expected: " (-to-s expected#) speclj.platform/endl " got: " (-to-s actual#) " (using ==)"))) + :else (throw (-new-exception (wrong-types "should==" expected# actual#))))))) (defmacro should-not== "Asserts 'non-equivalency'. When passed collections it will check that they do NOT have the same contents. For anything else it will assert that clojure.core/== returns false." [expected actual] - `(let [expected# ~expected - actual# ~actual] - (cond - (and (coll? expected#) (coll? actual#)) - (let [extra# (-coll-difference actual# expected#) - missing# (-coll-difference expected# actual#)] - (when (and (empty? extra#) (empty? missing#)) - (-fail (str "Expected contents: " (-to-s expected#) speclj.platform/endl " to differ from: " (-to-s actual#))))) - (and (number? expected#) (number? actual#)) - (when-not (not (== expected# actual#)) - (-fail (str " Expected: " (-to-s expected#) speclj.platform/endl "not to ==: " (-to-s actual#) " (using ==)"))) - :else (throw (-new-exception (wrong-types "should-not==" expected# actual#)))))) + `(help-should + (let [expected# ~expected + actual# ~actual] + (cond + (and (coll? expected#) (coll? actual#)) + (let [extra# (-coll-difference actual# expected#) + missing# (-coll-difference expected# actual#)] + (when (and (empty? extra#) (empty? missing#)) + (-fail (str "Expected contents: " (-to-s expected#) speclj.platform/endl " to differ from: " (-to-s actual#))))) + (and (number? expected#) (number? actual#)) + (when-not (not (== expected# actual#)) + (-fail (str " Expected: " (-to-s expected#) speclj.platform/endl "not to ==: " (-to-s actual#) " (using ==)"))) + :else (throw (-new-exception (wrong-types "should-not==" expected# actual#))))))) (defmacro should-not-be-nil "Asserts that the form evaluates to a non-nil value" @@ -547,7 +572,7 @@ (defmacro should-fail "Forces a failure. An optional message may be passed in." ([] `(should-fail "Forced failure")) - ([message] `(-fail ~message))) + ([message] `(help-should (-fail ~message)))) (defmacro ^:no-doc -create-should-throw-failure [expected actual expr] `(let [expected-name# (speclj.platform/type-name ~expected) @@ -565,14 +590,15 @@ There are three options for passing different kinds of predicates: - If a function, assert that calling the function on the Exception returns a truthy value." ([form] `(should-throw speclj.platform/throwable ~form)) ([throwable-type form] - `(try-catch-anything - ~form - (throw (-create-should-throw-failure ~throwable-type nil '~form)) - (catch e# - (cond - (speclj.error/failure? e#) (throw e#) - (not (instance? ~throwable-type e#)) (throw (-create-should-throw-failure ~throwable-type e# '~form)) - :else e#)))) + `(help-should + (try-catch-anything + ~form + (throw (-create-should-throw-failure ~throwable-type nil '~form)) + (catch e# + (cond + (speclj.error/failure? e#) (throw e#) + (not (instance? ~throwable-type e#)) (throw (-create-should-throw-failure ~throwable-type e# '~form)) + :else e#))))) ([throwable-type predicate form] `(let [e# (should-throw ~throwable-type ~form)] (try-catch-anything @@ -591,29 +617,32 @@ There are three options for passing different kinds of predicates: (defmacro should-not-throw "Asserts that nothing is thrown by the evaluation of a form." [form] - `(try-catch-anything - ~form - (catch e# - (-fail (str "Expected nothing thrown from: " (pr-str '~form) speclj.platform/endl - " but got: " (pr-str e#)))))) + `(help-should + (try-catch-anything + ~form + (catch e# + (-fail (str "Expected nothing thrown from: " (pr-str '~form) speclj.platform/endl + " but got: " (pr-str e#))))))) (defmacro should-be-a "Asserts that the type of the given form derives from or equals the expected type" [expected-type actual-form] - `(let [actual# ~actual-form - actual-type# (type actual#) - expected-type# ~expected-type] - (when-not (isa? actual-type# expected-type#) - (-fail (str "Expected " (-to-s actual#) " to be an instance of: " (-to-s expected-type#) speclj.platform/endl " but was an instance of: " (-to-s actual-type#) " (using isa?)"))))) + `(help-should + (let [actual# ~actual-form + actual-type# (type actual#) + expected-type# ~expected-type] + (when-not (isa? actual-type# expected-type#) + (-fail (str "Expected " (-to-s actual#) " to be an instance of: " (-to-s expected-type#) speclj.platform/endl " but was an instance of: " (-to-s actual-type#) " (using isa?)")))))) (defmacro should-not-be-a "Asserts that the type of the given form does not derive from or equal the expected type" [expected-type actual-form] - `(let [actual# ~actual-form - actual-type# (type actual#) - expected-type# ~expected-type] - (when (isa? actual-type# expected-type#) - (-fail (str "Expected " (-to-s actual#) " not to be an instance of " (-to-s expected-type#) " but was (using isa?)"))))) + `(help-should + (let [actual# ~actual-form + actual-type# (type actual#) + expected-type# ~expected-type] + (when (isa? actual-type# expected-type#) + (-fail (str "Expected " (-to-s actual#) " not to be an instance of " (-to-s expected-type#) " but was (using isa?)")))))) (defmacro pending "When added to a characteristic, it is marked as pending. If a message is provided it will be printed @@ -675,36 +704,37 @@ There are three options for passing different kinds of predicates: )" ([name] `(should-have-invoked ~name {})) ([name options] - `(let [name# ~name - options# ~options - invocations# (speclj.stub/invocations-of name#) - times# (:times options#) - times?# (number? times#) - check-params?# (contains? options# :with) - with# (:with options#) - with# (if (nil? with#) [] with#) - invocations-str# #(if (= 1 %) "invocation" "invocations")] - (cond - - (and times?# check-params?#) - (let [matching-invocations# (filter #(speclj.stub/params-match? with# %) invocations#) - matching-count# (count matching-invocations#)] - (when-not (= times# matching-count#) - (-fail (str "Expected: " times# " " (invocations-str# times#) " of " name# " with " (pr-str with#) speclj.platform/endl " got: " matching-count# " " (invocations-str# matching-count#))))) - - check-params?# - (when-not (some #(speclj.stub/params-match? with# %) invocations#) - (-fail (str "Expected: invocation of " name# " with " (pr-str with#) speclj.platform/endl " got: " (pr-str invocations#)))) - - times?# - (when-not (= times# (count invocations#)) - (-fail (str "Expected: " times# " " (invocations-str# times#) " of " name# speclj.platform/endl " got: " (count invocations#)))) - - :else - (when-not (seq invocations#) - (-fail (str "Expected: an invocation of " name# speclj.platform/endl " got: " (count invocations#)))) - - )))) + `(help-should + (let [name# ~name + options# ~options + invocations# (speclj.stub/invocations-of name#) + times# (:times options#) + times?# (number? times#) + check-params?# (contains? options# :with) + with# (:with options#) + with# (if (nil? with#) [] with#) + invocations-str# #(if (= 1 %) "invocation" "invocations")] + (cond + + (and times?# check-params?#) + (let [matching-invocations# (filter #(speclj.stub/params-match? with# %) invocations#) + matching-count# (count matching-invocations#)] + (when-not (= times# matching-count#) + (-fail (str "Expected: " times# " " (invocations-str# times#) " of " name# " with " (pr-str with#) speclj.platform/endl " got: " matching-count# " " (invocations-str# matching-count#))))) + + check-params?# + (when-not (some #(speclj.stub/params-match? with# %) invocations#) + (-fail (str "Expected: invocation of " name# " with " (pr-str with#) speclj.platform/endl " got: " (pr-str invocations#)))) + + times?# + (when-not (= times# (count invocations#)) + (-fail (str "Expected: " times# " " (invocations-str# times#) " of " name# speclj.platform/endl " got: " (count invocations#)))) + + :else + (when-not (seq invocations#) + (-fail (str "Expected: an invocation of " name# speclj.platform/endl " got: " (count invocations#)))) + + ))))) (defmacro should-not-have-invoked "Inverse of should-have-invoked. @@ -728,35 +758,36 @@ There are three options for passing different kinds of predicates: )" ([name] `(should-not-have-invoked ~name {})) ([name options] - `(let [name# ~name - options# ~options - invocations# (speclj.stub/invocations-of name#) - times# (:times options#) - times?# (number? times#) - check-params?# (contains? options# :with) - with# (:with options#) - with# (if (nil? with#) [] with#) - add-s# #(if (= 1 %) "" "s")] - (cond - (and times?# check-params?#) - (let [matching-invocations# (filter #(speclj.stub/params-match? with# %) invocations#) - matching-count# (count matching-invocations#)] - (when (= times# matching-count#) - (-fail (str "Expected: " name# " not to have been invoked " times# " time" (add-s# matching-count#) " with " (pr-str with#) speclj.platform/endl " got: " matching-count# " invocation" (add-s# matching-count#))))) - - times?# - (when (= times# (count invocations#)) - (-fail (str "Expected: " name# " not to have been invoked " times# " time" (add-s# times#) speclj.platform/endl " got: " times# " invocation" (add-s# times#)))) - - check-params?# - (when (some #(speclj.stub/params-match? with# %) invocations#) - (-fail (str "Expected: " name# " not to have been invoked with " (pr-str with#) speclj.platform/endl " got: " (pr-str invocations#)))) - - :else - (when (seq invocations#) - (-fail (str "Expected: 0 invocations of " name# speclj.platform/endl " got: " (count invocations#)))) - - )))) + `(help-should + (let [name# ~name + options# ~options + invocations# (speclj.stub/invocations-of name#) + times# (:times options#) + times?# (number? times#) + check-params?# (contains? options# :with) + with# (:with options#) + with# (if (nil? with#) [] with#) + add-s# #(if (= 1 %) "" "s")] + (cond + (and times?# check-params?#) + (let [matching-invocations# (filter #(speclj.stub/params-match? with# %) invocations#) + matching-count# (count matching-invocations#)] + (when (= times# matching-count#) + (-fail (str "Expected: " name# " not to have been invoked " times# " time" (add-s# matching-count#) " with " (pr-str with#) speclj.platform/endl " got: " matching-count# " invocation" (add-s# matching-count#))))) + + times?# + (when (= times# (count invocations#)) + (-fail (str "Expected: " name# " not to have been invoked " times# " time" (add-s# times#) speclj.platform/endl " got: " times# " invocation" (add-s# times#)))) + + check-params?# + (when (some #(speclj.stub/params-match? with# %) invocations#) + (-fail (str "Expected: " name# " not to have been invoked with " (pr-str with#) speclj.platform/endl " got: " (pr-str invocations#)))) + + :else + (when (seq invocations#) + (-fail (str "Expected: 0 invocations of " name# speclj.platform/endl " got: " (count invocations#)))) + + ))))) (def ^:dynamic ^:no-doc *bound-by-should-invoke* false) @@ -809,34 +840,38 @@ There are three options for passing different kinds of predicates: (defmacro should< "Asserts that the first numeric form is less than the second numeric form, using the built-in < function." [a b] - `(let [a# ~a b# ~b] - (if (and (number? a#) (number? b#)) - (when-not (< a# b#) (-fail (str "expected " a# " to be less than " b# " but got: (< " a# " " b# ")"))) - (throw (-new-exception (wrong-types "should<" a# b#)))))) + `(help-should + (let [a# ~a b# ~b] + (if (and (number? a#) (number? b#)) + (when-not (< a# b#) (-fail (str "expected " a# " to be less than " b# " but got: (< " a# " " b# ")"))) + (throw (-new-exception (wrong-types "should<" a# b#))))))) (defmacro should> "Asserts that the first numeric form is greater than the second numeric form, using the built-in > function." [a b] - `(let [a# ~a b# ~b] - (if (and (number? a#) (number? b#)) - (when-not (> a# b#) (-fail (str "expected " a# " to be greater than " b# " but got: (> " a# " " b# ")"))) - (throw (-new-exception (wrong-types "should>" a# b#)))))) + `(help-should + (let [a# ~a b# ~b] + (if (and (number? a#) (number? b#)) + (when-not (> a# b#) (-fail (str "expected " a# " to be greater than " b# " but got: (> " a# " " b# ")"))) + (throw (-new-exception (wrong-types "should>" a# b#))))))) (defmacro should<= "Asserts that the first numeric form is less than or equal to the second numeric form, using the built-in <= function." [a b] - `(let [a# ~a b# ~b] - (if (and (number? a#) (number? b#)) - (when-not (<= a# b#) (-fail (str "expected " a# " to be less than or equal to " b# " but got: (<= " a# " " b# ")"))) - (throw (-new-exception (wrong-types "should<=" a# b#)))))) + `(help-should + (let [a# ~a b# ~b] + (if (and (number? a#) (number? b#)) + (when-not (<= a# b#) (-fail (str "expected " a# " to be less than or equal to " b# " but got: (<= " a# " " b# ")"))) + (throw (-new-exception (wrong-types "should<=" a# b#))))))) (defmacro should>= "Asserts that the first numeric form is greater than or equal to the second numeric form, using the built-in >= function." [a b] - `(let [a# ~a b# ~b] - (if (and (number? a#) (number? b#)) - (when-not (>= a# b#) (-fail (str "expected " a# " to be greater than or equal to " b# " but got: (>= " a# " " b# ")"))) - (throw (-new-exception (wrong-types "should>=" a# b#)))))) + `(help-should + (let [a# ~a b# ~b] + (if (and (number? a#) (number? b#)) + (when-not (>= a# b#) (-fail (str "expected " a# " to be greater than or equal to " b# " but got: (>= " a# " " b# ")"))) + (throw (-new-exception (wrong-types "should>=" a# b#))))))) (defmacro run-specs [] "If evaluated outside the context of a spec run, it will run all the specs that have been evaluated using the default diff --git a/src/cljc/speclj/report/progress.cljc b/src/cljc/speclj/report/progress.cljc index 7d6fc4e..1a29fb3 100644 --- a/src/cljc/speclj/report/progress.cljc +++ b/src/cljc/speclj/report/progress.cljc @@ -3,7 +3,7 @@ [speclj.config :refer [*omit-pending?* default-reporters]] [speclj.error :as error] [speclj.platform :as platform] - [speclj.reporting :refer [green grey indent red stack-trace-str tally-time yellow]] + [speclj.reporting :refer [green grey indent red stack-trace-str tally-assertions tally-time yellow]] [speclj.results :refer [categorize]])) (defn full-name [characteristic] @@ -73,7 +73,8 @@ (defn describe-counts-for [result-map] (let [tally (zipmap (keys result-map) (map count (vals result-map))) always-on-counts [(str (apply + (vals tally)) " examples") - (str (:fail tally) " failures")]] + (str (:fail tally) " failures") + (str (tally-assertions (concat (:pass result-map) (:fail result-map))) " assertions")]] (str/join ", " (-> always-on-counts (apply-pending-tally tally) diff --git a/src/cljc/speclj/reporting.cljc b/src/cljc/speclj/reporting.cljc index 45d4061..78d5a88 100644 --- a/src/cljc/speclj/reporting.cljc +++ b/src/cljc/speclj/reporting.cljc @@ -5,8 +5,9 @@ [speclj.platform :refer [endl stack-trace cause error-str print-stack-trace elide-level?]] [speclj.results :refer [pass? fail?]])) -(defn tally-time [results] - (apply + (map #(.-seconds %) results))) +(defn- sum-by [f coll] (apply + (map f coll))) +(defn tally-time [results] (sum-by #(.-seconds %) results)) +(defn tally-assertions [results] (sum-by #(.-assertions %) results)) (defprotocol Reporter (report-message [reporter message]) diff --git a/src/cljc/speclj/results.cljc b/src/cljc/speclj/results.cljc index 084f7aa..f4f3732 100644 --- a/src/cljc/speclj/results.cljc +++ b/src/cljc/speclj/results.cljc @@ -1,15 +1,15 @@ (ns speclj.results) -(deftype PassResult [characteristic seconds]) -(deftype FailResult [characteristic seconds failure]) +(deftype PassResult [characteristic seconds assertions]) +(deftype FailResult [characteristic seconds failure assertions]) (deftype PendingResult [characteristic seconds exception]) (deftype ErrorResult [characteristic seconds exception]) -(defn pass-result [characteristic seconds] - (PassResult. characteristic seconds)) +(defn pass-result [characteristic seconds assertions] + (PassResult. characteristic seconds assertions)) -(defn fail-result [characteristic seconds failure] - (FailResult. characteristic seconds failure)) +(defn fail-result [characteristic seconds failure assertions] + (FailResult. characteristic seconds failure assertions)) (defn pending-result [characteristic seconds exception] (PendingResult. characteristic seconds exception)) @@ -27,9 +27,9 @@ (defn categorize [results] (reduce (fn [tally result] (cond - (pending? result) (update-in tally [:pending] conj result) - (error? result) (update-in tally [:error] conj result) - (fail? result) (update-in tally [:fail] conj result) - :else (update-in tally [:pass] conj result))) + (pending? result) (update tally :pending conj result) + (error? result) (update tally :error conj result) + (fail? result) (update tally :fail conj result) + :else (update tally :pass conj result))) {:pending [] :fail [] :pass [] :error []} - results)) \ No newline at end of file + results)) diff --git a/src/cljc/speclj/running.cljc b/src/cljc/speclj/running.cljc index 60a7821..be3d0b2 100644 --- a/src/cljc/speclj/running.cljc +++ b/src/cljc/speclj/running.cljc @@ -99,36 +99,37 @@ (recur @(.-parent description) (concat (getter description) components)) components))) -(defn- report-result [result-constructor characteristic start-time reporters failure] - (let [present-args (filter identity [characteristic (secs-since start-time) failure]) +(defn- report-result [result-constructor characteristic start-time reporters failure assertions] + (let [present-args (filter identity [characteristic (secs-since start-time) failure assertions]) result (apply result-constructor present-args)] (report-run result reporters) result)) (defn- do-characteristic [characteristic reporters] - (let [description @(.-parent characteristic) - befores (collect-components #(deref (.-befores %)) description) - afters (collect-components #(deref (.-afters %)) description) - core-body (.-body characteristic) - before-and-after-body (fn [] (eval-characteristic befores core-body afters)) - arounds (collect-components #(deref (.-arounds %)) description) - full-body (nested-fns before-and-after-body (map #(.-body %) arounds)) - withs (collect-components #(deref (.-withs %)) description) - start-time (current-time)] - (try - (do + (binding [components/*assertions* (atom 0)] + (let [description @(.-parent characteristic) + befores (collect-components #(deref (.-befores %)) description) + afters (collect-components #(deref (.-afters %)) description) + core-body (.-body characteristic) + before-and-after-body (fn [] (eval-characteristic befores core-body afters)) + arounds (collect-components #(deref (.-arounds %)) description) + full-body (nested-fns before-and-after-body (map #(.-body %) arounds)) + withs (collect-components #(deref (.-withs %)) description) + start-time (current-time)] + (try (full-body) - (report-result pass-result characteristic start-time reporters nil)) - (catch #?(:clj java.lang.Throwable :cljr Exception :cljs :default) e - (if (pending? e) - (report-result pending-result characteristic start-time reporters e) - (report-result fail-result characteristic start-time reporters e))) - (finally - (reset-withs withs))))) ;MDM - Possible clojure bug. Inlining reset-withs results in compile error + (report-result pass-result characteristic start-time reporters nil @components/*assertions*) + (catch #?(:clj java.lang.Throwable :cljr Exception :cljs :default) e + (if (pending? e) + (report-result pending-result characteristic start-time reporters e nil) + (report-result fail-result characteristic start-time reporters e @components/*assertions*))) + (finally + (reset-withs withs)))))) ;MDM - Possible clojure bug. Inlining reset-withs results in compile error (defn- do-characteristics [characteristics reporters] (doall - (for [characteristic characteristics :when (can-run? characteristic)] + (for [characteristic characteristics + :when (can-run? characteristic)] (do-characteristic characteristic reporters)))) (declare do-description) @@ -177,18 +178,19 @@ (when (can-run? description) (let [tag-sets (tag-sets-for description)] (when (some pass-tag-filter? tag-sets) - (report-description* reporters description) - (with-withs-bound description - (fn [] - (eval-components @(.-before-alls description)) - - (try - (with-around-alls - description - (partial nested-results-for-context description reporters)) - - (finally - (reset-withs @(.-with-alls description)))))))))) + (binding [components/*assertions* (atom 0)] + (report-description* reporters description) + (with-withs-bound description + (fn [] + (eval-components @(.-before-alls description)) + + (try + (with-around-alls + description + (partial nested-results-for-context description reporters)) + + (finally + (reset-withs @(.-with-alls description))))))))))) (defn process-compile-error [runner e] (let [error-result (error-result e)] (swap! (.-results runner) conj error-result) From 22bfdd1153d738874717650993f1995cef7f758e Mon Sep 17 00:00:00 2001 From: Brandon Correa <50252804+brandoncorrea@users.noreply.github.com> Date: Wed, 26 Feb 2025 13:03:54 -0600 Subject: [PATCH 2/2] Update changelog --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 76e5aed..3c5f22a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,6 @@ +# Next +* Displays the total number of assertions made in the test results + # 3.6.0 * Replaces `SpecFailure` and `SpecPending` classes with `ex-info` * Replaces `mmargs` library with a Clojure implementation