{"id":13396426,"url":"https://github.com/benrady/specific","last_synced_at":"2025-10-21T22:03:32.686Z","repository":{"id":62431758,"uuid":"72052805","full_name":"benrady/specific","owner":"benrady","description":"Generate mocks and other test doubles using clojure.spec","archived":false,"fork":false,"pushed_at":"2019-02-10T19:14:04.000Z","size":71,"stargazers_count":32,"open_issues_count":4,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-10-21T22:01:40.898Z","etag":null,"topics":["clojure","mock-functions","tdd","testing"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benrady.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-10-26T23:25:30.000Z","updated_at":"2024-05-31T07:57:53.000Z","dependencies_parsed_at":"2022-11-01T21:00:37.907Z","dependency_job_id":null,"html_url":"https://github.com/benrady/specific","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/benrady/specific","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benrady%2Fspecific","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benrady%2Fspecific/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benrady%2Fspecific/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benrady%2Fspecific/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benrady","download_url":"https://codeload.github.com/benrady/specific/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benrady%2Fspecific/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280341244,"owners_count":26314177,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","mock-functions","tdd","testing"],"created_at":"2024-07-30T18:00:50.978Z","updated_at":"2025-10-21T22:03:32.655Z","avatar_url":"https://github.com/benrady.png","language":"Clojure","funding_links":[],"categories":["Tests \u0026 Instrumentation"],"sub_categories":[],"readme":"# Specific\n\nGenerate mocks and other test doubles using clojure.spec\n\n## Why?\n\nTesting code with side effects, such as I/O, can be painful. It slows down your tests and can cause spurious failures. Mocking out these interactions is a great way to keep your tests fast and reliable.\n\n_Specific_ can generate mock functions from [clojure.spec](http://clojure.org/about/spec) definitions. It can help you make assertions about how the functions were called, or simply remove the side effect and let your spec declarations do the verification. This means it works on programs with example-based tests, [property-based](https://github.com/clojure/test.check) generative tests, or both.\n\n## Dependencies\n\n_Specific_ works with Clojure 1.10 or 1.9 (or 1.8 with the [clojure.spec backport](https://github.com/tonsky/clojure-future-spec)) and [test.check](https://github.com/clojure/test.check) version 0.9.0.\n\nYou can find the latest version in the Clojars repository, here:\n\n[![Clojars Project](https://img.shields.io/clojars/v/com.benrady/specific.svg)](https://clojars.org/com.benrady/specific)\n\n## Usage\n\nTo show you how to use _Specific_, let's assume you have three interdependent functions you'd like to test. One of them, `cowsay`, executes a shell command which might not be available in all environments.\n\n```clojure\n(ns sample\n  (:require [clojure.java.shell :as shell]\n            [clojure.string :as string]))\n\n(defn greet [pre sufs]\n  (string/join \", \" (cons pre sufs)))\n\n(defn cowsay [msg]\n  (shell/sh \"cowsay\" msg)) ; Fails in some environments\n\n(defn some-fun [greeting \u0026 names]\n  (:out (cowsay (greet greeting names))))\n```\n\n_Specific_ works best with functions that have clojure.spec [definitions](http://clojure.org/guides/spec#_spec_ing_functions). You can include these definitions with the code under test, or you can add them in the tests themselves, or both.\n\n```clojure\n(clojure.spec/def ::exit (clojure.spec/and integer? #(\u003e= % 0) #(\u003c % 256)))\n(clojure.spec/def ::out string?)\n(clojure.spec/def ::fun-greeting string?)\n(clojure.spec/fdef greet :ret ::fun-greeting)\n(clojure.spec/fdef cowsay\n                   :args (clojure.spec/cat :fun-greeting ::fun-greeting)\n                   :ret (clojure.spec/keys :req-un [::out ::exit]))\n(clojure.spec/fdef some-fun\n                   :args (clojure.spec/+ string?)\n                   :ret string?)\n```\n\n### Mock Functions\n\nMocking a function prevents the original function from being called, which is useful when you want to prevent side effects in a test, but still want to ensure it was invoked properly. Mocked functions validate their arguments against the specs defined for the original function, and return data generated from the spec. \n\nYou can replace a list of functions with mock functions using the `specific.core/with-mocks` macro, like so:\n\n```clojure\n(testing \"mock functions\"\n  (with-mocks [sample/cowsay]\n\n    (testing \"return a value generated from the spec\"\n      (is (\u003c= 0 (:exit (sample/cowsay \"hello\"))))\n      (is string? (:out (sample/cowsay \"hello\"))))\n\n    (testing \"validate against the spec of the original function\"\n      (sample/cowsay \"hello\"))\n\n      ; (sample/cowsay 1)\n      ; val: 1 fails spec: :specific.sample/fun-greeting at: [:args 0] predicate: string?\n      ;\n      ; expected: string?\n      ;   actual: 1\n\n    (testing \"record the individual calls\"\n      (sample/cowsay \"hello\")\n      (sample/cowsay \"world\")\n      (is (= [[\"hello\"] [\"world\"]] (calls sample/cowsay))))))\n```\n\n### Test Conforming Arguments\n\nIf you want to make assertions about how a particular mock was invoked, you can use `specific.core/calls` to get list of arguments for all the invocations of any _Specific_ mock function. While easy to understand and extensible, this approach would require that you use generated values in your tests. Instead, you can assert that the arguments passed to a function conform to a spec, using `specific.core/args-conform`:\n\n```clojure\n(testing \"args-conform matcher\"\n  (spec/def ::h-word #(string/starts-with? % \"h\"))\n  (with-mocks [sample/cowsay]\n\n    (testing \"matches with exact values\"\n      (sample/some-fun \"hello\" \"world\") \n      (is (args-conform sample/cowsay \"hello, world\")))\n\n    (testing \"can use a custom spec to validate an argument\"\n      (sample/some-fun \"hello\" \"world\")\n      (sample/some-fun \"hello\" \"larry\")\n      (is (args-conform sample/cowsay ::h-word)))\n\n    (testing \"can ensure all invocations are conforming\"\n      (doall ; Ironically, exercise is lazy\n        (spec/exercise-fn `sample/some-fun))\n      (is (args-conform sample/cowsay ::sample/fun-greeting)))))\n```\n\nThe args-conform matcher is also handy when you need to verify invocations that include generated data returned from a mock or stub. You can use any spec that you want to verify the arguments. You can also mix specs and exact values in a single call.\n\n### Stub Functions\n\nStub functions are more lenient than mocks, not requiring the function to have a spec. Stub functions always return nil.\n\n```clojure\n(testing \"stub functions\"\n  (with-stubs [clojure.java.shell/sh]\n\n    (testing \"return nil\"\n      (is (nil? (sample/some-fun \"hello\" \"world\"))))\n\n    (testing \"don't need a spec\"\n      (sample/some-fun \"hello\" \"world\")\n      (is (args-conform clojure.java.shell/sh \"cowsay\" ::sample/fun-greeting)))))\n```\n\nJust as with mocks, when using the args-conform matcher on a stub, you can use specs, exact values, or a mixture of the two\n\n### Spy Functions\n\nSpy functions call through to the original function, but still record the calls and enforce the constraints in the function's spec.\n\n```clojure\n(testing \"spy functions\"\n  (with-spies [sample/greet]\n\n    (testing \"calls through to the original function\"\n      (is (= \"Hello, World!\" (sample/greet \"Hello\" [\"World!\"])))\n      (is (= [[\"Hello\" [\"World!\"]]] (calls sample/greet))))))\n```\n\nIn practice, spies in _Specific_ work a lot like the default behavior of [clojure.spec/instrument](https://clojure.github.io/clojure/branch-master/clojure.spec-api.html#clojure.spec.test/instrument), except that they are scoped only to the forms in the `with-spies` macro.\n\n### Generated data\n\nYou can use specs to generate test data, optionally overriding certain specs to produce different combinations of values.\n\n```clojure\n  (testing \"generating test data\"\n    (spec/def ::word (spec/and string? #(re-matches #\"\\w+\" %)))\n    (spec/def ::short-string (spec/and ::word #(\u003e (count %) 2) #(\u003c (count %) 5)))\n\n    (testing \"Returns a constant, conforming value for a given spec\"\n      (is (= \"koI\" (generate ::short-string)))\n      (is (spec/valid? ::short-string (generate ::short-string))))\n\n    (testing \"can override specs\"\n      (is (= \"word\" (generate ::short-string ::word #{\"word\"}))))\n\n    (testing \"uses with-gens overrides too\"\n      (with-gens [::word #{\"word\"}]\n        (is (= \"word\" (generate ::short-string))))))\n```\n\nUnlike the regular test.check generator, data generated in _Specific_ test doubles is deterministic. This is true for both the `generate` function and mocks. This means the values generated will not change unless spec itself changes. Whether or not you depend on this consistency is up to you.\n\n### Generator Overrides\n\nSometimes, within the scope of a test (or a group of tests) it makes sense to override the generator for a spec. For example, you want to test a more specific range of values, or have a function return a single value. To do that with _Specific_ you can use the `with-gens` macro:\n\n```clojure\n(testing \"generator overrides\"\n  (with-mocks [sample/cowsay sample/greet]\n\n    (testing \"can temporarily replace the generator for a spec using a predicate\"\n      (with-gens [::sample/fun-greeting #{\"hello!\"}]\n        (is (= \"hello!\" (sample/greet \"hello\" [])))))\n\n    (testing \"can replace the generator for a nested value\"\n      (with-gens [::sample/exit #{0}]\n        (is (= 0 (:exit (sample/cowsay \"hello\"))))))\n\n    (testing \"can use another spec's generator\"\n      (with-gens [::sample/out ::sample/fun-greeting]\n        (is (string? (sample/some-fun \"hello\"))))))))\n```\n\nSince with-gens redefines the generator for a spec, and not an entire function, you can use it to specify a portion of an otherwise default generated return value (a single nested `:phone-number` value in an entity map, for example).\n\n## Friends and Relations\n\n_Specific_ gets along well with the following tools:\n  * [lein-test-refresh](https://github.com/jakemcc/lein-test-refresh) by [Jake McCrary](http://jakemccrary.com/)\n  * [humane-test-output](https://github.com/pjstadig/humane-test-output) by Paul Stadig\n  * [test.chuck](https://github.com/gfredericks/test.chuck) by [Gary Fredericks](http://gfredericks.com/)\n\n## Changelog\n\n0.6.0\n  * Support for Clojure 1.10, 1.9, and 1.8\n  * More sensible error messages when you forget to mock a function\n\n0.5.0 \n  * Renamed conforming to args-conform\n  * No longer evaluating forms when a mock cannot be created\n  * Better failure messages when missing a :ret spec in a mock\n\n0.4.0 \n  * Generated values are now deterministic\n  * Added core/generate to generate test data\n\n## Developing\n\nThe following commands run the tests against various versions of Clojure.\n\n```clojure\nlein with-profile +1.10 test\nlein with-profile +1.9 test\nlein with-profile +1.8 test\n```\n\n## License\n\nCopyright (C) 2016 Ben Rady \u003cbenrady@gmail.com\u003e\n\nThis program is free software; you can redistribute it and/or modify it under the terms of the [GNU General Public License version 2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) as published by the Free Software Foundation.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenrady%2Fspecific","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenrady%2Fspecific","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenrady%2Fspecific/lists"}