Simpliest pattern matching you've ever seen.
The main goal is to provide the simpliest DSL to describe pattern of expected value.
Not so easy to write tests with multiple asserts. Code grows fast, a lot of repetitions, hard to read.
(ns hello-world.core-test
(:require [clojure.test :refer :all]
[clojure.spec.alpha :as s]
[matcho.core :refer :all :as m]))
(defn patch-article [patch]
{:status 200
:body (assoc {:article (merge patch
{:text "nice article text"})}
:article-id 1
:meta {:tags ["nature" "bears"]})})
(def patch {:title "Article about bears"
:description "Very good article"})
(def resp (patch-article patch))
(s/def ::str-coll (s/coll-of string?))
(deftest general-patch-test
(let [body (:body resp)]
(is (< (:status resp) 300))
(is (= (:title patch) (get-in body [:article :title])))
(is (= (:description patch) (get-in body [:article :description])))
(is (s/valid? ::str-coll (get-in body [:meta :tags])))))
More declarative and readable approach:
(m/assert pattern value)
(deftest matcho-patch-test
(def pattern
{:status #(< % 300)
:body {:article patch
:meta {:tags ::str-coll}}})
(m/assert pattern resp))
Full example can be found here.
Add following project dependency to deps.edn:
{healthsamurai/matcho {:mvn/version "RELEASE"}}
Understand and pick out needed parts:
(ns hello-world.core
(:require [clojure.test :refer :all]
[clojure.spec.alpha :as s]
[matcho.core :refer :all :as m]))
There are three main vars in core ns: valid?
, explain-data
and assert
.
First one is a function, which takes pattern and value returns true if value
conforms the pattern and false in other case. The second one is a function,
which returns a vector of errors or nil. The last one is a macro, it works the
same way as valid?
, but additionally asserts with is
and provide a vector of
errors using expalin-data
.
(m/valid? [int? string?] [1 "test"])
;; => true
(deftest int-str-pair-test
(m/assert [int? string?] [1 "test"]))
(m/explain-data [int? int? string?] [1 "test"])
;; => [{:expected "#function[clojure.core/int?]", :but "test", :path [1]} {:expected "#function[clojure.core/string?--5132]", :but nil, :path [2]}]
(deftest int-str-pair-fail-test
(m/assert [int? int? string?] [1 "test"]))
;; [{:expected "#function[clojure.core/int?]", :but "test", :path [1]} {:expected "#function[clojure.core/string?--5132]", :but nil, :path [2]}] [1 "test"] [[#function[clojure.core/int?] #function[clojure.core/int?] #function[clojure.core/string?--5132]]]
matcho
takes a data structure with "special" leaf nodes (a pattern) and
smartly compares them with corresponding nodes in the original value. If nodes
looks too different an explanation will be added to list of errors and the
process will continue.
The pattern can be much smaller (has less keys, elements in vector and so on)
than the original value and it is a common case. The open-world
assumption implemented in
matcho
allows developer to check only "interesting" parts.
(m/valid? {:status 200} {:status 200 :body "ok"})
;; => true
(m/valid? {:status 200 :body string?} {:status 200})
;; => false
There are several options for pattern leaf values. It can be:
(s/def ::pos-coll (s/coll-of pos?))
(deftest readme-test
(is (m/valid? pos? 1))
(m/assert 1 1)
(m/assert {:status #(< % 300)
:body #(not (empty? %))}
{:status 200
:body "hello"})
(m/assert ::pos-coll [1 2 3])
(m/assert [{:expected #"conforms.*pos-coll"}]
(m/explain-data ::pos-coll [1 -1 2])))
More advanced examples can be found here.
Because matcho
, that's why.
(def response {:status 200
:body "ok"})
(deftest with-spec-test
(s/def ::status #(= 200 %))
(s/def ::body #(not-empty %))
(s/def ::response (s/keys :req-un [::status ::body]))
(is (s/valid? ::response response)))
(deftest without-spec-test
(m/assert {:status 200 :body not-empty} response))
Copyright © 2016 HealthSamurai
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close