Skip to content

Instantly share code, notes, and snippets.

@philomates
Created September 13, 2019 09:48
Show Gist options
  • Save philomates/3ae99114e9f39fa6030eeac2119bd08b to your computer and use it in GitHub Desktop.
Save philomates/3ae99114e9f39fa6030eeac2119bd08b to your computer and use it in GitHub Desktop.
match-with: a strange translation saga
(ns match-with.a-strange-translation-saga-test
(:require [clojure.test :refer :all]
[matcher-combinators.core :as core]
[matcher-combinators.matchers :refer [in-any-order]]
[matcher-combinators.test :refer [match? match-with?]]))
;; ----------------------------------------------------------------------
;; Lightning talk presented at the Clojure Berlin meetup on Sept 12 2019
;; as a means to demo `match-with?` a new feature to matcher-combinators
;; ----------------------------------------------------------------------
;; I work at a company that uses a fair amount of portuguese slang
;; so sometimes I get confused and mix up portuguese and english
;; Let's say I find a server endpoint that returns this:
(defn get-slack-word-freq []
{:status 200
:body [{:word "rataria" :freq 0.99}
{:word "xunxo" :freq 0.9}]})
;; and now I want to exercise it with some tests:
(deftest naively-I-write
(testing "but I forget to translate some words"
(is (= ["hack" "rataria"]
(->> (get-slack-word-freq)
:body
(map :word))))))
;; I don't like those diffs, so I pick up some matcher-combinators
(deftest I-try-again-with-help
(testing "and get a clearer failure message and order-agnostic matching"
(is (match? {:body (in-any-order [{:word "hack"}
{:word "rataria"}])}
(get-slack-word-freq)))))
;; luckily someone wrote a dictionary for me
(def portuguese->english
{"xunxo" "hack"
"rataria" "???"
"gambiarra" "monkey-patch"})
;; so if I ever swap the language of a word, I can always run it through this
(defn translates? [a b]
(or (= a b)
(= b (portuguese->english a))
(= a (portuguese->english b))))
;; Now, I want something that matches results but doesn't care if I accidently
;; use the equivalent of the word in the other language
(deftest this-is-the-cleanest-I-could-do-after-10-minutes
(testing "not using matcher-combinators"
(let [;; boiler plate to help write this test
english->portuguese (->> portuguese->english
(map (fn [[k v]] [v k]))
(into {}))
language-agnostic (fn [a] (->> a
((juxt identity
portuguese->english
english->portuguese))
(remove nil?)
set))
expecteds (map language-agnostic
["rataria" "hack"])]
;; we check that every word in the result satisfies the language-agnostic
;; predicates we built
(is (every?
string?
(->> (get-slack-word-freq)
:body
(map :word)
(map (fn [e w] (e w)) expecteds)))))))
;; But with `match-with?`, a new feature in matcher-combinators, we can do a little better
(defn translates-match [expected]
;; the ergonomics of this isn't great, later I'll try to make it easier to define custom matchers
(core/->PredMatcher
(fn [actual] (translates? actual expected))))
(deftest final-step-of-our-saga
(testing "redefining string matching to use translations via matcher-combinators"
(is (match-with? {java.lang.String translates-match}
{:body (in-any-order [{:word "hack"}
{:word "rataria"}])}
(get-slack-word-freq)))))
;; of course there are more "useful" applications, like non-exact number
;; matching, or forcing maps to be matched exactly instead of the default that
;; ignores extra keys
;; Check out the docs for more info:
;; https://github.com/nubank/matcher-combinators/#overriding-default-matchers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment