├── .clj-kondo ├── config.edn └── imports │ └── com.github.seancorfield │ └── expectations │ ├── config.edn │ └── hooks │ └── com │ └── github │ └── seancorfield │ └── expectations.clj_kondo ├── .github ├── FUNDING.yml └── workflows │ ├── test-and-release.yml │ ├── test-and-snapshot.yml │ └── test.yml ├── .gitignore ├── .gitpod.dockerfile ├── .gitpod.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── build.clj ├── deps.edn ├── doc ├── cljdoc.edn ├── collections.md ├── fixtures-focus.md ├── getting-started-cljs.md ├── getting-started.md ├── more.md ├── side-effects.md └── useful-predicates.md ├── dooopts.edn ├── planckopts.edn ├── resources └── clj-kondo.exports │ └── com.github.seancorfield │ └── expectations │ ├── config.edn │ └── hooks │ └── com │ └── github │ └── seancorfield │ └── expectations.clj_kondo ├── src └── expectations │ └── clojure │ └── test.cljc └── test └── expectations └── clojure ├── test_macros.cljc ├── test_spec.cljs └── test_test.cljc /.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {:linters 2 | {:consistent-alias 3 | {:aliases 4 | ;; straw man for some standardization: 5 | {clojure.string str}} 6 | 7 | :missing-docstring {:level :warning} 8 | 9 | :single-key-in {:level :warning} 10 | 11 | :unsorted-required-namespaces {:level :error}} 12 | 13 | :config-paths ["com.github.seancorfield/expectations"]} 14 | -------------------------------------------------------------------------------- /.clj-kondo/imports/com.github.seancorfield/expectations/config.edn: -------------------------------------------------------------------------------- 1 | {:hooks 2 | {:analyze-call 3 | {expectations.clojure.test/more-> 4 | hooks.com.github.seancorfield.expectations/more-> 5 | expectations.clojure.test/more-of 6 | hooks.com.github.seancorfield.expectations/more-of}} 7 | :lint-as 8 | {expectations.clojure.test/defexpect clojure.test/deftest 9 | expectations.clojure.test/from-each clojure.core/for 10 | expectations.clojure.test/=? clojure.core/=}} 11 | -------------------------------------------------------------------------------- /.clj-kondo/imports/com.github.seancorfield/expectations/hooks/com/github/seancorfield/expectations.clj_kondo: -------------------------------------------------------------------------------- 1 | (ns hooks.com.github.seancorfield.expectations 2 | (:require [clj-kondo.hooks-api :as api])) 3 | 4 | (defn more-> [{:keys [node]}] 5 | (let [tail (rest (:children node)) 6 | rewritten 7 | (api/list-node 8 | (list* 9 | (api/token-node 'cond->) 10 | (api/token-node 'nil) 11 | tail))] 12 | {:node rewritten})) 13 | 14 | (defn more-of [{:keys [node]}] 15 | (let [bindings (fnext (:children node)) 16 | pairs (partition 2 (nnext (:children node))) 17 | rewritten 18 | (api/list-node 19 | (list* 20 | (api/token-node 'fn) 21 | (api/vector-node (vector bindings)) 22 | (map (fn [[e a]] 23 | (api/list-node 24 | (list 25 | (api/token-node 'expectations.clojure.test/expect) 26 | e 27 | a))) 28 | pairs)))] 29 | {:node rewritten})) 30 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: seancorfield 2 | -------------------------------------------------------------------------------- /.github/workflows/test-and-release.yml: -------------------------------------------------------------------------------- 1 | name: Release Version 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - uses: actions/setup-java@v4 16 | with: 17 | distribution: 'adopt' 18 | java-version: '11' 19 | - name: Install Planck 20 | run: sudo add-apt-repository -y ppa:mfikes/planck && sudo apt-get update -y && sudo apt-get install -y planck 21 | - name: Clojure CLI 22 | uses: DeLaGuardo/setup-clojure@master 23 | with: 24 | cli: '1.12.0.1488' 25 | - name: Cache All The Things 26 | uses: actions/cache@v4 27 | with: 28 | path: | 29 | ~/.m2/repository 30 | ~/.gitlibs 31 | ~/.clojure 32 | ~/.cpcache 33 | key: ${{ runner.os }}-${{ hashFiles('**/deps.edn') }} 34 | - name: Run Tests 35 | run: clojure -T:build ci :cljs true :snapshot false 36 | - name: Deploy Release 37 | run: clojure -T:build deploy :snapshot false 38 | env: 39 | CLOJARS_PASSWORD: ${{secrets.DEPLOY_TOKEN}} 40 | CLOJARS_USERNAME: ${{secrets.DEPLOY_USERNAME}} 41 | -------------------------------------------------------------------------------- /.github/workflows/test-and-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Develop & Snapshot 2 | 3 | on: 4 | push: 5 | branches: 6 | - "develop" 7 | 8 | jobs: 9 | build-and-snapshot: 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-java@v4 14 | with: 15 | distribution: 'adopt' 16 | java-version: '11' 17 | - name: Install Planck 18 | run: sudo add-apt-repository -y ppa:mfikes/planck && sudo apt-get update -y && sudo apt-get install -y planck 19 | - name: Clojure CLI 20 | uses: DeLaGuardo/setup-clojure@master 21 | with: 22 | cli: '1.12.0.1488' 23 | - name: Cache All The Things 24 | uses: actions/cache@v4 25 | with: 26 | path: | 27 | ~/.m2/repository 28 | ~/.gitlibs 29 | ~/.clojure 30 | ~/.cpcache 31 | key: ${{ runner.os }}-${{ hashFiles('**/deps.edn') }} 32 | - name: Run Tests 33 | run: clojure -T:build ci :cljs true :snapshot true 34 | - name: Deploy Snapshot 35 | run: clojure -T:build deploy :snapshot true 36 | env: 37 | CLOJARS_PASSWORD: ${{secrets.DEPLOY_TOKEN}} 38 | CLOJARS_USERNAME: ${{secrets.DEPLOY_USERNAME}} 39 | 40 | build: 41 | runs-on: ubuntu-latest 42 | strategy: 43 | matrix: 44 | java: [ '17', '20' ] 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: actions/setup-java@v4 48 | with: 49 | distribution: 'adopt' 50 | java-version: ${{ matrix.java }} 51 | - name: Clojure CLI 52 | uses: DeLaGuardo/setup-clojure@master 53 | with: 54 | cli: '1.12.0.1488' 55 | - name: Cache All The Things 56 | uses: actions/cache@v4 57 | with: 58 | path: | 59 | ~/.m2/repository 60 | ~/.gitlibs 61 | ~/.clojure 62 | ~/.cpcache 63 | key: ${{ runner.os }}-${{ hashFiles('**/deps.edn') }} 64 | - name: Run Tests 65 | run: clojure -T:build test 66 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-20.04 8 | strategy: 9 | matrix: 10 | java: [ '11', '17', '20' ] 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-java@v4 14 | with: 15 | distribution: 'adopt' 16 | java-version: ${{ matrix.java }} 17 | - name: Install Planck 18 | run: sudo add-apt-repository -y ppa:mfikes/planck && sudo apt-get update -y && sudo apt-get install -y planck 19 | - name: Clojure CLI 20 | uses: DeLaGuardo/setup-clojure@master 21 | with: 22 | cli: '1.12.0.1488' 23 | - name: Cache All The Things 24 | uses: actions/cache@v4 25 | with: 26 | path: | 27 | ~/.m2/repository 28 | ~/.gitlibs 29 | ~/.clojure 30 | ~/.cpcache 31 | key: ${{ runner.os }}-${{ hashFiles('**/deps.edn') }} 32 | - name: Run Tests 33 | run: clojure -T:build ci :cljs true 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.jar 3 | *.swp 4 | *~ 5 | .DS_Store 6 | .calva/output-window/ 7 | .calva/repl.calva-repl 8 | .classpath 9 | .clj-kondo/.cache 10 | .cpcache 11 | .eastwood 12 | .factorypath 13 | .hg/ 14 | .hgignore 15 | .java-version 16 | .lein-* 17 | .lsp/.cache 18 | .lsp/sqlite.db 19 | .nrepl-history 20 | .nrepl-port 21 | .portal 22 | .project 23 | .rebel_readline_history 24 | .settings 25 | .socket-repl-port 26 | .sw* 27 | /checkouts 28 | /classes 29 | /cljs-test-runner-out 30 | /target 31 | -------------------------------------------------------------------------------- /.gitpod.dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | 3 | RUN brew install clojure/tools/clojure@1.10.3.933 4 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.dockerfile 3 | 4 | vscode: 5 | extensions: 6 | - betterthantomorrow.calva 7 | - mauricioszabo.clover 8 | 9 | tasks: 10 | - name: Prepare deps/clover 11 | init: | 12 | clojure -A:test -P 13 | echo 50505 > .socket-repl-port 14 | mkdir ~/.config/clover 15 | cp .clover/config.cljs ~/.config/clover/ 16 | - name: Start REPL 17 | command: clojure -J-Dclojure.server.repl="{:address \"0.0.0.0\" :port 50505 :accept clojure.core.server/repl}" -A:test 18 | - name: See Changes 19 | command: code CHANGELOG.md 20 | 21 | github: 22 | prebuilds: 23 | develop: true 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | > NOTE: Since Clojars introduced a [Verified Group Names policy](https://github.com/clojars/clojars-web/wiki/Verified-Group-Names), no new libraries could be pushed to the `expectations` group, and `doo` filters out JAR artifacts that begin `clojure-` for self-hosted ClojureScript environments (i.e., `planck`), so continuing to use `clojure-test` for the artifact name is not viable. Accordingly, the 2.x versions of this library are published as `com.github.seancorfield/expectations`. 4 | 5 | ## Stable Releases 6 | 7 | Only accretive/fixative changes will be made from now on. 8 | 9 | * 2.3.next in progress 10 | * Address [#42](https://github.com/clojure-expectations/clojure-test/issues/42) by removing support for the implicit `expect` form of `defexpect`. This was deprecated in 2.2.214. In 2.3.next, an exception is thrown. 11 | * Updated dev/test dependencies. 12 | 13 | * 2.2.214 -- 2024-12-13 14 | * Address [#41](https://github.com/clojure-expectations/clojure-test/issues/41) by deprecating the implicit `expect` form of `defexpect` and printing an obnoxious warning when it is used. This form of `defexpect` will be removed in a future release! 15 | * PR [#40](https://github.com/clojure-expectations/clojure-test/pull/40) [@NoahTheDuke](https://github.com/NoahTheDuke) reduces the amount of code generated for `?=` (and, in turn, for `more-of`), allowing for more complex tests. 16 | 17 | * 2.1.208 -- 2024-11-21 18 | * Address [#39](https://github.com/clojure-expectations/clojure-test/issues/39) by ignoring qualifier on Expectations' macros. This is potentially breaking but is often required during migration to another test framework, as you may have to use aliased names to avoid collisions (to LazyTest, for example). 19 | * Address [#36](https://github.com/clojure-expectations/clojure-test/issues/36) by expanding `side-effects` documentation, and mentioning `with-redefs`. 20 | * Expose `run-test` and `run-test-var` from `clojure.test` (Clojure 1.11). 21 | * Update dev/test dependencies. 22 | 23 | * 2.1.201 -- 2024-05-07 24 | * Address [#35](https://github.com/clojure-expectations/clojure-test/issues/35) by treating any symbol starting with `expect` as an Expectations macro for the 2-argument form. 25 | * Address [#33](https://github.com/clojure-expectations/clojure-test/issues/33) by expanding the README introduction. 26 | * Address [#31](https://github.com/clojure-expectations/clojure-test/issues/31) by adding more examples to `more-of`. 27 | * Update dev/test dependencies. 28 | 29 | * 2.1.188 -- 2023-10-22 30 | * Address [#29](https://github.com/clojure-expectations/clojure-test/issues/29) by providing a "hook" for `more-of`. 31 | * Update `tools.build` to 0.9.6 (and get rid of `template/pom.xml` in favor of new `:pom-data` option to `b/write-pom`). 32 | 33 | * 2.1.182 -- 2023-09-29 34 | * Improved failure reporting: most failures now provide an additional message describing the failure as well as improving how the expected and actual values are displayed (primarily hiding `=?` and showing a more accurate/intuitive test form). 35 | * Update `deps.edn` to use `:main-args` (instead of `:main-opts`) for parameterized tasks in `build.clj` -- see [Running Tasks based on Aliases](https://clojure-doc.org/articles/cookbooks/cli_build_projects/) 36 | * Drop support for Java 8 (it may continue to work but it is no longer tested). 37 | * Update dependencies to latest stable versions. 38 | 39 | * 2.0.165 -- 2023-01-31 40 | * Fix [#30](https://github.com/clojure-expectations/clojure-test/issues/30) by removing `build-clj` and using raw `tools.build`. 41 | * Address [#29](https://github.com/clojure-expectations/clojure-test/issues/29) by providing a "hook" for `more->` (but more work is needed). 42 | * Address [#27](https://github.com/clojure-expectations/clojure-test/issues/27) by changing `refer`'d note in stub macros' docstrings. 43 | 44 | * 2.0.160 -- 2022-03-26 45 | * Fix [#28](https://github.com/clojure-expectations/clojure-test/issues/28) by recognizing qualified calls to `expect` (to suppress legacy behavior in more cases). 46 | * Update `build-clj` to v0.8.0. 47 | 48 | * 2.0.157 -- 2022-01-25 49 | * Fix a small regression in how classes are treated when used as predicates in `expect`. 50 | 51 | * 2.0.156 -- 2022-01-19 52 | * Address [#26](https://github.com/clojure-expectations/clojure-test/issues/26) by adding an example combining `more->` and `more-of` around destructuring `ex-info` data. 53 | * Fix [#24](https://github.com/clojure-expectations/clojure-test/issues/24) by using a local (gensym) for the actual value in `more` and `more->` so it is only evaluated once. 54 | * Update `build-clj` to v0.6.7 and automate snapshot/release builds. 55 | 56 | * 2.0.143 -- 2021-12-01 57 | * Fix #23 by adding support for set-`in`-set expectations. 58 | * Documentation updates. 59 | * Build deps updates. 60 | 61 | * 2.0.137 -- 2021-11-07 62 | * Address #22 by adding `clj-kondo.exports` (this is just a first pass; the `:lint-as` mappings will probably be replaced by hooks in the future). 63 | * Fix #19 by supporting regex/patterns dynamically inside `=?` (as well as the compile-time support already in `expect`). 64 | * Update `build-clj` to v0.5.0. 65 | * Switch to `build.clj`/`tools.build` for CI/deploy. 66 | 67 | ## 2.0.x Prereleases 68 | 69 | * 2.0.0-alpha2 -- 2021-06-09 70 | * Mostly a documentation refresh, compared to Alpha 1. 71 | 72 | * 2.0.0-alpha1 -- 2021-06-05 73 | * Make `(defexpect foo)` and `(defexpect foo (bar))` match the behavior of `deftest`, without wrapping the body in `(expect ,,,)`. This is potentially breaking insofar as `(defexpect foo (produces-falsey))` would have been a failing test in 1.x but now silently just runs `(produces-falsey)` in the same way that `(deftest foo (produces-falsey))` does. 74 | * Bring in several test-running functions from `clojure.test`, for convenience in dev/test so users don't need to require `clojure.test` as well. 75 | * Implement `cljs.test`'s version of `use-fixtures`: accepts functions or hash maps (containing `:before` and/or `:after` keys with 0-arity functions). 76 | * Add various macro-like constructs back into the source code to improve the documentation (`in`, `from-each`, `more-of`, `more->`, `more` are really only syntactic constructs inside `expect`). 77 | * Support (self-hosted) ClojureScript via `planck` -- see https://github.com/clojure-expectations/clojure-test/pull/16 for details (@kkinear). 78 | 79 | ## Previous Releases 80 | 81 | These versions required users to also require `clojure.test` and were not as 82 | directly comparable to `clojure.test` behaviors. 83 | 84 | * 1.2.1 -- 2019-12-09 85 | * Fix cljdoc.org index (Collections was missing). 86 | 87 | * 1.2.0 -- 2019-12-09 88 | * Improve failure reporting for `in`; allow it to be combined with `more` etc. #11 89 | * Add support for mocking return values in `side-effects`. 90 | * Add support for optional message argument in `expect`. #9 91 | * Added article-style documentation for cljdoc.org. #6, #7, #8, #10 92 | * Add example of `more->` equivalent to `thrown-with-msg?`. #5 93 | 94 | * 1.1.2 -- 2019-12-07 95 | * Adds `between` and `between'` for inclusive and exclusive range checking. 96 | * Fix `in` with a hash map to correctly detect failing cases. 97 | * Add a first round of tests (finally!). Verified support for Clojure 1.8 (without Spec expectations). Verified full support for Clojure 1.9 and 1.10.1. 98 | * Clean up `:require` .. `:refer` in README to list all public symbols. #4 99 | * Fixes links in README. PR #3 (@marekjeszka) 100 | * Add/improve docstrings. Add `^:no-doc` metadata for cljdoc.org. 101 | 102 | * 1.1.1 -- 2019-01-14 103 | * An expectation can now use a qualified keyword spec to test conformance of the actual value. Failures are reported with the spec explanation. #2 104 | * If Paul Stadig's Humane Test Output is available (on the classpath), failure reporting is automatically made compatible with it. Expectations that use data structure "equality" (the `=?` extension to `is`) will produce "humane" output for failures, showing differences. #1 105 | 106 | * 1.1.0 -- 2019-01-08 107 | * (broken version) 108 | 109 | * 1.0.1 -- 2019-01-02 110 | * Initial version 111 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at sean@corfield.org. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `clojure.test` for Expectations [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/clojure-expectations/clojure-test) 2 | 3 | A `clojure.test`-compatible version of the [classic Expectations testing library](https://clojure-expectations.github.io/). 4 | 5 | ## Where? 6 | 7 | [![Clojars](https://img.shields.io/badge/clojars-com.github.seancorfield/expectations_2.2.214-red.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABjFBMVEUAAAAdCh0qDikdChwAAAAnDSY0EjM2FjUnDiYnDSYnDSYpDigyEDEEAQRGNUb///////8mDSYAAAAAAAAAAAAFAgUqEyoAAAAAAAAAAAAFAgUAAABXU1c2FjVMx+dQx+f///////9Nx+b////4/f6y4vRPt+RQtOT///9Qt+P///8oDSey4vRQr9/////3/P5hzelNx+dNx+dNx+f///8AAAAuDy0zETIAAAAoDScAAAAAAAARBREAAAAvDy40ETMwEC9gSF+Ne42ilKKuoK6Rg5B5ZXlaP1o4Gzf///9nTWZ4YncyEDF/bn/8/Pz9/P339/c1FTUlDCRRM1AbCRtlS2QyEDEuDy1gRWAxEDAzETIwEC/g4OAvDy40EjOaiZorDiq9sbzNyM3UzdQyEDE0ETMzETKflZ/UzdQ5Fzmu4fNYyuhNx+dPt+RLu9xQyOhBbo81GTuW2vCo4PJNx+c4MFE5N1lHiLFEhKQyEDGDboMzETI5Fjh5bXje2d57aHrIw8jc2NyWhJUrDioxe9o4AAAAPnRSTlMAkf+IAQj9+e7n6e31RtqAD/QAAAED+A0ZEQ8DwvkLBsmcR4aG8+cdAD6C8/MC94eP+qoTrgH+/wj1HA8eEvpXOCUAAAABYktHRA8YugDZAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wcHFjou4Z/shwAAAUpJREFUOMul0/VTwzAUB/AAwyW4y3B3h8EDNuTh7u6UDHcd8I+TbHSjWdrjju/1h77kc+3Lu5aQvyakF/r6B5wu1+DQMEBomLRtG0EpozYDCEccA4iIjIqOiY0bB5iYxHgZ4FQCpYneKmmal0aQPMOXZnUAvJhLkbpInf8NFtKCTrGImK6DJcTlDGl/BXGV6oCsrSNIYAM3aQDwl2xJYBtBB5lZAuyYgWzY3YMcNcjN2wc4EGMEFTg8+hlyfgEenygAj71Q9FBExH0wKC4p1bRTJlJWXqEAVNM05ovbXfkPAHBmAUQPAGaAsXMBLiwA8z3h0gRcsWsObuAWLJu8Awb3ZoB5T8EvS/CgBo9Y5Z8TPwXBJwlUI9Ia/yRrEZ8lID71Olrf0MiamkkL4kurDEjba+C/e2sninR0wrsH8eMTvrqIWbodjh7jyjdtCY3Aniz4jwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0wNy0wN1QyMjo1ODo0NiswMjowMCgWtSoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTUtMDctMDdUMjI6NTg6NDYrMDI6MDBZSw2WAAAAAElFTkSuQmCC)](https://clojars.org/com.github.seancorfield/expectations) 8 | [![cljdoc badge](https://cljdoc.org/badge/com.github.seancorfield/expectations?2.2.214)](https://cljdoc.org/d/com.github.seancorfield/expectations/CURRENT) 9 | [![Slack](https://img.shields.io/badge/slack-Expectations-red.svg?logo=slack)](https://clojurians.slack.com/app_redirect?channel=expectations) 10 | [![Join Slack](https://img.shields.io/badge/slack-join_clojurians-red.svg?logo=slack)](http://clojurians.net) 11 | 12 | Try it out: 13 | 14 | ``` 15 | clj -Sdeps '{:deps {com.github.seancorfield/expectations {:mvn/version "RELEASE"}}}' 16 | ``` 17 | 18 | ## What? 19 | 20 | This library provides a more expressive way to write tests than `clojure.test`, 21 | while still being fully compatible with `clojure.test` and all its tooling. 22 | 23 | While `clojure.test` provides basic assertions using `(is (= ... ...))` 24 | and `(is (thrown? ... ...))`, Expectations additionally supports predicates, 25 | regular expressions, Specs, and collection-based tests. 26 | 27 | You can either "mix'n'match" `clojure.test` and `expectations.clojure.test` 28 | features in your tests, using `deftest` from `clojure.test` or `defexpect` from 29 | this library to wrap your tests, or you can use `expectations.clojure.test` on 30 | its own, since it exposes equivalents to all of the top-level `clojure.test` 31 | functions and macros for dealing with fixtures and running tests. 32 | 33 | The following are equivalent: 34 | 35 | ```clojure 36 | (deftest my-test-1 37 | (is (= 2 (+ 1 1))) 38 | (is (thrown? ArithmeticException (/ 1 0)))) 39 | 40 | (defexpect my-test-2 41 | (expect 2 (+ 1 1)) 42 | (expect ArithmeticException (/ 1 0))) 43 | ``` 44 | 45 | But you can also do things like: 46 | 47 | ```clojure 48 | (defexpect my-test-3 49 | (expect even? (+ 1 1)) 50 | (expect #"foo" "It's foobar!") 51 | (expect ::adult-age 42)) ; ::adult-age is a Spec 52 | ``` 53 | 54 | See the example REPL session below for more details. 55 | 56 | This library has no dependencies, other than `clojure.test` itself, and 57 | should be compatible with all existing `clojure.test`-based tooling in editors 58 | and command-line tools. 59 | 60 | Works with Clojure 1.9 and later. 61 | 62 | Works in self-hosted ClojureScript (specifically, 63 | [`planck`](https://planck-repl.org)). See 64 | [Getting Started with ClojureScript](/doc/getting-started-cljs.md) for details. 65 | 66 | > I consider Expectations to be mature and stable, but at this point somewhat in 67 | maintenance mode. I have started migrating several of my own projects to 68 | [LazyTest](https://github.com/noahtheduke/lazytest) which is actively maintained 69 | and provides better reporting and a more expressive test DSL. LazyTest has 70 | a [`lazytest.extensions.expectations`](https://cljdoc.org/d/io.github.noahtheduke/lazytest/CURRENT/api/lazytest.extensions.expectations) 71 | namespace that provides a fairly complete replacement for Expectations on 72 | Clojure (no ClojureScript support) so it's fairly easy to migrate from Expectations 73 | to LazyTest. Note that LazyTest is **not** compatible with `clojure.test` 74 | tooling -- it has its own test runner and reporting -- which may color your 75 | decision to, especially if you rely on Cursive's built-in test runner (or 76 | Calva's -- although custom REPL snippets mitigate that, at least for me). 77 | 78 | ## Example REPL Session 79 | 80 | What follows is an example REPL session showing some of what this library provides. For more detailed documentation, start with [Getting Started](/doc/getting-started.md) and work your way through the sections listed there. 81 | 82 | ```clojure 83 | (ns my.cool.project-test 84 | (:require [clojure.spec.alpha :as s] 85 | [clojure.test :refer [deftest is]] 86 | [expectations.clojure.test 87 | :refer [defexpect expect expecting 88 | approximately between between' functionally 89 | side-effects]])) 90 | 91 | ;; mix'n'match libraries: 92 | 93 | (deftest mixed 94 | (is (= 2 (+ 1 1))) 95 | (expect even? (+ 1 1))) 96 | 97 | ;; simple equality tests: 98 | 99 | (defexpect equality 100 | (expect 1 (* 1 1)) 101 | (expect "foo" (str "f" "oo"))) 102 | 103 | ;; the expected outcome can be a regular expression: 104 | 105 | (defexpect regex-1 106 | (expect #"foo" "It's foobar!")) 107 | 108 | ;; the expected outcome can be an exception type: 109 | 110 | (defexpect divide-by-zero 111 | (expect ArithmeticException (/ 12 0))) 112 | 113 | ;; the expected outcome can be a predicate: 114 | 115 | (defexpect no-elements 116 | (expect empty? (list))) 117 | 118 | ;; the expected outcome can be a type: 119 | 120 | (defexpect named 121 | (expect String (name :foo))) 122 | 123 | ;; the expected outcome can be a Spec: 124 | 125 | (s/def ::value (s/and pos-int? #(< % 100))) 126 | (defexpect small-value 127 | (expect ::value (* 13 4))) 128 | 129 | ;; if the actual value is a collection, the expected outcome can be an element or subset "in" that collection: 130 | 131 | (defexpect collections 132 | (expect {:foo 1} (in {:foo 1 :cat 4})) 133 | (expect :foo (in #{:foo :bar})) 134 | (expect :foo (in [:bar :foo]))) 135 | 136 | ;; just like clojure.test's testing macro to label groups of tests 137 | ;; you can use expecting to label groups of expectations (this uses 138 | ;; some of more advanced features listed below): 139 | 140 | (defexpect grouped-behavior 141 | (expecting "numeric behavior" 142 | (expect (more-of {:keys [a b]} 143 | even? a 144 | odd? b) 145 | {:a (* 2 13) :b (* 3 13)}) 146 | (expect pos? (* -3 -5))) 147 | (expecting "string behavior" 148 | (expect (more #"foo" "foobar" #(clojure.string/starts-with? % "f")) 149 | (str "f" "oobar")) 150 | (expect #"foo" 151 | (from-each [s ["l" "d" "bar"]] 152 | (str "foo" s))))) 153 | ``` 154 | 155 | Just like `deftest`, the `defexpect` macro creates a function that contains the test(s). You can run each function individually: 156 | 157 | ```clojure 158 | user=> (equality) 159 | nil 160 | ``` 161 | 162 | If the test passes, nothing is printed, and `nil` is returned. Let's look at a failing test: 163 | 164 | ```clojure 165 | user=> (defexpect inequality (expect (* 2 21) (+ 13 13 13))) 166 | #'user/inequality 167 | user=> (inequality) 168 | 169 | FAIL in (inequality) (.../README.md:117) 170 | expected: (=? (* 2 21) (+ 13 13 13)) 171 | actual: (not (=? 42 39)) 172 | nil 173 | ``` 174 | 175 | The output is produced by `clojure.test`'s standard reporting functionality. 176 | The `=?` operator is an extension to `clojure.test`'s `assert-expr` multimethod 177 | that allows for Expectations style of predicate-or-equality testing (based on 178 | whether the "expected" expression resolves to a function or some other value): 179 | 180 | ```clojure 181 | user=> (defexpect not-at-all-odd (expect odd? (+ 1 1))) 182 | #'user/not-at-all-odd 183 | user=> (not-at-all-odd) 184 | 185 | FAIL in (not-at-all-odd) (.../README.md:133) 186 | expected: (=? odd? (+ 1 1)) 187 | actual: (not (odd? 2)) 188 | nil 189 | ``` 190 | 191 | Here we see the predicate (`odd?`) being applied in the "actual" result from 192 | `clojure.test`. 193 | 194 | Just like the `is` macro, `expect` can take an optional failure message as the third argument: 195 | 196 | ```clojure 197 | user=> (defexpect failure-msg 198 | (expect even? (+ 1 1 1) "It's uneven!")) 199 | #'user/failure-msg 200 | user=> (failure-msg) 201 | 202 | FAIL in (failure-msg) (.../README.md:149) 203 | It's uneven! 204 | expected: (=? even? (+ 1 1 1)) 205 | actual: (not (even? 3)) 206 | nil 207 | ``` 208 | 209 | ## Why? 210 | 211 | TL;DR: Because I liked the ["Classic" Expectations library](https://clojure-expectations.github.io) but didn't like having to use custom, Expectations-specific tooling. 212 | 213 | ### Why not just use `clojure.test`? 214 | 215 | `clojure.test` is a great library for writing tests in Clojure. It's simple, 216 | it's built-in, and it's widely supported by the Clojure ecosystem. However, it 217 | only provides basic assertions and doesn't support some of the more advanced 218 | testing features that Expectations does. 219 | 220 | ### Why not just use the "Classic" Expectations library? 221 | 222 | Given the streamlined simplicity of Expectations, you might wonder why you 223 | would want to migrate your Expectations test suite to `clojure.test`-style 224 | named tests? The short answer is **tooling**! Whilst Expectations has 225 | well-maintained, stable plugins for Leiningen and Boot, as well as an Emacs mode, 226 | the reality is that Clojure tooling is constantly evolving and most of those 227 | tools -- such as the excellent [CIDER](https://docs.cider.mx/), 228 | [Cursive](https://cursive-ide.com/), 229 | [Calva](https://calva.io/) (for VS Code), 230 | and [Cognitect's `test-runner`](https://github.com/cognitect-labs/test-runner) -- 231 | are going to focus on Clojure's built-in testing library first. 232 | Support for the original form of Expectations, using unnamed tests, is 233 | non-existent in Cursive, and can be problematic in other editors and tooling. 234 | 235 | A whole ecosystem 236 | of tooling has grown up around `clojure.test` and to take advantage of 237 | that with Expectations, we either need to develop compatible extensions to each 238 | and every tool or we need Expectations to be compatible with `clojure.test`. 239 | 240 | One of the big obstacles for that compatibility is that, by default, Expectations 241 | generates "random" function names for test code (the function names are based on the 242 | hashcode of the text form of the `expect` body), which means the test 243 | name changes whenever the text of the test changes. To address that, the new 244 | `expectations.clojure.test` namespace introduces named expectations via 245 | the `defexpect` macro (mimicking `clojure.test`'s `deftest` 246 | macro). Whilst this goes against the [Test Names 247 | philosophy](https://clojure-expectations.github.io/odds-ends.html) that Expectations was created with, it buys us a lot in terms of 248 | tooling support! 249 | 250 | ## Compatibility with "Classic" Expectations 251 | 252 | `expectations.clojure.test` supports the following features from the "Classic" Expectations library so far: 253 | 254 | * simple equality test 255 | * simple predicate test 256 | * spec test (using a keyword that identifies a spec) 257 | * class test -- see `named` above 258 | * exception test -- see `divide-by-zero` above 259 | * regex test -- see `regex-1` and `regex-2` above 260 | * `(expect expected-expr (from-each [a values] (actual-expr a)))` 261 | * `(expect expected-expr (in actual-expr))` -- see `collections` above 262 | * `(expect (more-of destructuring e1 a1 e2 a2 ...) actual-expr)` 263 | * `(expect (more-> e1 a1 e2 a2 ...) actual-expr)` -- where `actual-expr` is threaded into each `a1`, `a2`, ... expression 264 | * `(expect (more e1 e2 ...) actual-expr)` 265 | * `(expect expected-expr (side-effects [fn1 fn2 ...] actual-expr))` 266 | 267 | Read [the Expectations documentation](https://clojure-expectations.github.io/) 268 | for more details of these features. 269 | 270 | ## Differences from "Classic" Expectations 271 | 272 | Aside from the obvious difference of providing names for tests -- essential for 273 | compatibility with `clojure.test`-based tooling -- here are the other differences 274 | to be aware of: 275 | 276 | * You use standard `clojure.test`-based tooling -- `lein test`, `boot test`, and [Cognitect's `test-runner`](https://github.com/cognitect-labs/test-runner) -- instead of the Expectations-specific tooling. 277 | * Because of that, tests run when you decide, not at JVM shutdown (which is the default with Expectations). 278 | * If you have [Paul Stadig's Humane Test Output](https://github.com/pjstadig/humane-test-output) on your classpath, it will be activated and failures reported by `=?` will be compatible with it, providing better reporting. 279 | * Instead of the `in-context`, `before-run`, `after-run` machinery of Expectations, you can just use `clojure.test`'s fixtures machinery (`use-fixtures`). As of 2.0.0, `use-fixtures` is exposed directly via `expectations.clojure.test` so you don't need to require `clojure.test`. 280 | * Instead of Expectations' concept of "focused" test, you can use metadata on tests and tell your test runner to "select" tests as needed (e.g., Leiningen's "test selectors", Boot's "filters", and `test-runner`'s `-i`/`-e` options). 281 | * `freeze-time`, `redef-state`, and `warn-on-iref-updates` are not (yet) implemented. 282 | * The undocumented `CustomPred` protocol is not implemented -- you can use plain `is` and extend `clojure.test`'s `assert-expr` multimethod if you need that level of control. 283 | 284 | ## Test & Development 285 | 286 | To test, run `clj -X:test` (tests against Clojure 1.9). 287 | 288 | Multi-version testing: 289 | 290 | ``` 291 | for v in 1.9 1.10 1.11 1.12 292 | do 293 | clojure -X:test:$v 294 | done 295 | ``` 296 | 297 | You can also run the tests with Humane Test Output enabled but you need to exclude the negative tests because they assume things about the test report data that HTO modifies: 298 | 299 | ``` 300 | for v in 1.9 1.10 1.11 1.12 301 | do 302 | clojure -X:test:$v:humane :excludes '[:negative]' 303 | done 304 | ``` 305 | 306 | ### ClojureScript testing 307 | 308 | The ClojureScript version requires self-hosted ClojureScript (specifically, 309 | [`planck`](https://planck-repl.org)). Once you have `planck -h` working, 310 | you can run the ClojureScript tests with: 311 | 312 | ```clojure 313 | clojure -M:cljs-runner -e :negative 314 | ``` 315 | You can run the negative tests as well if you modify one line of `test.cljc`, 316 | see the comments below the line containing `(def humane-test-output?`. 317 | 318 | #### ClojureScript REPL 319 | 320 | It can be handy to try things in a REPL. You can run a REPL for ClojureScript 321 | by doing: 322 | ```clojure 323 | $ planck --compile-opts planckopts.edn -c `clojure -Spath -A:humane` -r 324 | ClojureScript 1.10.520 325 | cljs.user=> (require '[expectations.clojure.test :refer-macros [defexpect expect]]) 326 | nil 327 | cljs.user=> (defexpect a (expect number? 1)) 328 | #'cljs.user/a 329 | cljs.user=> (a) 330 | nil 331 | cljs.user=> (defexpect a (expect number? :b)) 332 | #'cljs.user/a 333 | cljs.user=> (a) 334 | 335 | FAIL in (a) (run_block@file:44:173) 336 | 337 | 338 | expected: (=? number? :b) 339 | actual: (not (number? :b)) 340 | nil 341 | cljs.user=> 342 | ``` 343 | This will set you up with `defexpect` and `expect`. Add others as required. 344 | 345 | 346 | ## License & Copyright 347 | 348 | Copyright © 2018-2024 Sean Corfield, all rights reserved. 349 | 350 | Distributed under the Eclipse Public License version 1.0. 351 | -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | "Expections (clojure-test) build script. 3 | 4 | Run individual round of tests: 5 | 6 | clojure -T:build run-tests 7 | clojure -T:build run-tests :aliases '[:1.12]' 8 | 9 | Run multi-version tests: 10 | 11 | clojure -T:build test 12 | 13 | Also run cljs tests: 14 | 15 | clojure -T:build test :cljs true 16 | 17 | Run the CI pipeline (to build a JAR): 18 | 19 | clojure -T:build ci 20 | 21 | For more information, run: 22 | 23 | clojure -A:deps -T:build help/doc" 24 | (:refer-clojure :exclude [test]) 25 | (:require [clojure.string :as str] 26 | [clojure.tools.build.api :as b] 27 | [clojure.tools.deps :as t] 28 | [deps-deploy.deps-deploy :as dd])) 29 | 30 | (def lib 'com.github.seancorfield/expectations) 31 | (defn- the-version [patch] (format "2.3.%s" patch)) 32 | (def version (the-version (b/git-count-revs nil))) 33 | (def snapshot (the-version "999-SNAPSHOT")) 34 | (def class-dir "target/classes") 35 | 36 | (defn run-tests "Run the tests." 37 | [{:keys [aliases] :as opts}] 38 | (println "\nRunning tests for" (str/join ", " (map name aliases)) "...") 39 | (let [basis (b/create-basis {:aliases (into [:test] aliases)}) 40 | combined (t/combine-aliases basis (into [:test] aliases)) 41 | cmds (b/java-command 42 | {:basis basis 43 | :main 'clojure.main 44 | :main-args (cond-> (:main-args combined) 45 | (some #{:humane :cljs} aliases) 46 | (into ["-e" ":negative"]))}) 47 | {:keys [exit]} (b/process cmds)] 48 | (when-not (zero? exit) (throw (ex-info "Tests failed" {})))) 49 | opts) 50 | 51 | (defn test "Run all the tests." [opts] 52 | (reduce (fn [opts alias] 53 | (run-tests (assoc opts :aliases [alias]))) 54 | opts 55 | (cond-> [:1.9 :1.10 :1.11 :1.12 :humane] 56 | (:cljs opts) 57 | (conj :cljs))) 58 | opts) 59 | 60 | (defn- pom-template [version] 61 | [[:description "A clojure.test-compatible version of the classic Expectations testing library."] 62 | [:url "https://github.com/clojure-expectations/clojure-test"] 63 | [:licenses 64 | [:license 65 | [:name "Eclipse Public License"] 66 | [:url "http://www.eclipse.org/legal/epl-v10.html"]]] 67 | [:developers 68 | [:developer 69 | [:name "Sean Corfield"]]] 70 | [:scm 71 | [:url "https://github.com/clojure-expectations/clojure-test"] 72 | [:connection "scm:git:https://github.com/clojure-expectations/clojure-test.git"] 73 | [:developerConnection "scm:git:ssh:git@github.com:clojure-expectations/clojure-test.git"] 74 | [:tag (str "v" version)]]]) 75 | 76 | (defn- jar-opts [opts] 77 | (let [version (if (:snapshot opts) snapshot version)] 78 | (assoc opts 79 | :lib lib :version version 80 | :jar-file (format "target/%s-%s.jar" lib version) 81 | :basis (b/create-basis {}) 82 | :class-dir class-dir 83 | :target "target" 84 | :src-dirs ["src"] 85 | :pom-data (pom-template version)))) 86 | 87 | (defn ci "Run the CI pipeline of tests (and build the JAR)." [opts] 88 | (test opts) 89 | (b/delete {:path "target"}) 90 | (let [opts (jar-opts opts)] 91 | (println "\nWriting pom.xml...") 92 | (b/write-pom opts) 93 | (println "\nCopying source...") 94 | (b/copy-dir {:src-dirs ["resources" "src"] :target-dir class-dir}) 95 | (println "\nBuilding JAR" (:jar-file opts) "...") 96 | (b/jar opts)) 97 | opts) 98 | 99 | (defn deploy "Deploy the JAR to Clojars." [opts] 100 | (let [{:keys [jar-file] :as opts} (jar-opts opts)] 101 | (dd/deploy {:installer :remote :artifact (b/resolve-path jar-file) 102 | :pom-file (b/pom-path (select-keys opts [:lib :class-dir]))})) 103 | opts) 104 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:mvn/repos {"sonatype" {:url "https://oss.sonatype.org/content/repositories/snapshots/"}} 2 | :paths ["src" "resources"] 3 | :deps {org.clojure/clojure {:mvn/version "1.9.0"}} 4 | :aliases 5 | {;; for help: clojure -A:deps -T:build help/doc 6 | :build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.6"} 7 | slipset/deps-deploy {:mvn/version "0.2.2"}} 8 | :ns-default build} 9 | 10 | ;; versions to test against: 11 | :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} 12 | :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}} 13 | :1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.4"}}} 14 | :1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0"}}} 15 | 16 | :kondo ; for debugging hooks 17 | {:extra-deps {clj-kondo/clj-kondo {:mvn/version "RELEASE"}}} 18 | ;; running tests/checks of various kinds: 19 | :test ; can also run clojure -X:test 20 | {:extra-paths ["test"] 21 | :extra-deps {io.github.cognitect-labs/test-runner 22 | {:git/tag "v0.5.1" :git/sha "dfb30dd"} 23 | org.clojure/core.cache {:mvn/version "RELEASE"}} 24 | :exec-fn cognitect.test-runner.api/test 25 | ;; for build.clj: 26 | :main-args ["-m" "cognitect.test-runner"]} 27 | 28 | :humane 29 | {:extra-deps {pjstadig/humane-test-output {:mvn/version "RELEASE"}}} 30 | 31 | :cljs 32 | {:extra-deps {olical/cljs-test-runner {:mvn/version "3.8.1"} 33 | pjstadig/humane-test-output {:mvn/version "0.11.0"}} 34 | :extra-paths ["src" "test" "cljs-test-runner-out/gen"] 35 | :main-args ["-m" "cljs-test-runner.main" "--doo-opts" 36 | "dooopts.edn" "-x" "planck"]}}} 37 | -------------------------------------------------------------------------------- /doc/cljdoc.edn: -------------------------------------------------------------------------------- 1 | {:cljdoc.doc/tree [["Readme" {:file "README.md"}] 2 | ["Changes" {:file "CHANGELOG.md"}] 3 | ["Getting Started" {:file "doc/getting-started.md"}] 4 | ["Getting Started in ClojureScript" {:file "doc/getting-started-cljs.md"}] 5 | ["Collections" {:file "doc/collections.md"}] 6 | ["Useful Predicates" {:file "doc/useful-predicates.md"}] 7 | ["Expecting More" {:file "doc/more.md"}] 8 | ["Expecting Side Effects" {:file "doc/side-effects.md"}] 9 | ["Fixtures & Focused Execution" {:file "doc/fixtures-focus.md"}]]} 10 | -------------------------------------------------------------------------------- /doc/collections.md: -------------------------------------------------------------------------------- 1 | # Collections 2 | 3 | While you can use Clojure's standard functions to manipulate collections of data to build up your tests, Expectations provides some conveniences to make life a bit easier: `in` and `from-each`. 4 | 5 | ## `expect in` 6 | 7 | If you have a function that returns a hash map but you only care about testing certain parts of it, you can write an expectation like this: 8 | 9 | ```clojure 10 | (expect {:membership/status "active" :membership/type "platinum"} 11 | (in (lookup-membership db-spec test-user))) 12 | ``` 13 | 14 | This says "we expect the following key/value pairs to be in the result". 15 | 16 | You can also use `in` with sets, lists, and vectors: 17 | 18 | ```clojure 19 | (expect "platinum" (in (membership-types db-spec))) 20 | ``` 21 | 22 | It's worth noting that the default output from `in` can be somewhat confusing and this is definitely a case where Humane Test Output helps: 23 | 24 | ```clojure 25 | (expect {:foo 1} (in {:bar 2})) 26 | 27 | ;; default output 28 | FAIL in () (...:...) 29 | (in {:bar 2}) 30 | 31 | expected: (expect {:foo 1} (in {:bar 2})) 32 | actual: (not= {:foo 1} {}) 33 | 34 | ;; with Humane Test Output: 35 | FAIL in () (...:...) 36 | (in {:bar 2}) 37 | 38 | expected: {:foo 1} 39 | actual: {} 40 | diff: - {:foo 1} 41 | ``` 42 | 43 | This is an area that will be improved in the future. 44 | 45 | ## `expect from-each` 46 | 47 | If you have an expectation that should apply to every element of a collection, such as expecting all `active-users` to have memberships that have an `"active"` status, we can write this: 48 | 49 | ```clojure 50 | (expect {:membership/status "active"} 51 | (from-each [user (find-active-users db-spec)] 52 | (in (lookup-membership db-spec user)))) 53 | ``` 54 | 55 | `from-each` is effectively a shorthand for a test that would otherwise look like this: 56 | 57 | ```clojure 58 | (doseq [user (find-active-users db-spec)] 59 | (expect {:membership/status "active"} 60 | (in (lookup-membership db-spec user)))) 61 | ``` 62 | 63 | A simpler `from-each` example: 64 | 65 | ```clojure 66 | (expect even? (from-each [n (range 10)] (* 2 n))) 67 | ``` 68 | 69 | `from-each` also supports `:let` and `:when` syntax just like `for` and `doseq`. 70 | 71 | See also [Expecting More](/doc/more.md) for examples of combining `from-each` 72 | with other Expectations for powerful multi-valued tests. 73 | 74 | # Further Reading 75 | 76 | * [Getting Started](/doc/getting-started.md) 77 | * [Useful Predicates](/doc/useful-predicates.md) 78 | * [Expecting More](/doc/more.md) 79 | * [Expecting Side Effects](/doc/side-effects.md) 80 | * [Fixtures & Focused Test Execution](/doc/fixtures-focus.md) 81 | -------------------------------------------------------------------------------- /doc/fixtures-focus.md: -------------------------------------------------------------------------------- 1 | # Fixtures & Focused Test Execution 2 | 3 | When tests are run -- by Leiningen, Boot, Cognitect's `test-runner`, or your IDE/editor -- it can be useful to perform setup and teardown around those tests, as well as electing to run only a subset of those tests. Those features are known as "fixtures" and "focused tests" (or "selected tests") respectively. 4 | 5 | Test fixtures are a way to run arbitrary code before and/or after each test or each group of tests. The classic Expectations library used metadata on certain functions to indicate that they should be run before the entire suite of tests, after the entire suite of tests, or around each individual expectation (test). Since this new Expectations library leans on `clojure.test` for test running infrastructure, it assumes you will use `clojure.test`'s features to provide test fixtures. 6 | 7 | Focused tests are identified in the code somehow so that your test runner can execute just a subset of all the tests, when needed. The classic Expectations library used a special `expect-focused` macro to signal that just a subset of tests should be run. This new Expectations library follows the convention of `clojure.test`-based tooling instead, relying on metadata on test functions to signal to all the standard tooling the various test selectors available for the runner to filter on. 8 | 9 | ## Test Fixtures 10 | 11 | To use test fixtures with Expectations, you can refer `use-fixtures` (which, as of 2.0.0, accepts both functions -- like `clojure.test/use-fixtures` -- and hash maps containing `:before` and/or `:after` keys that specify 0-arity functions -- like `cljs.test/use-fixtures`). For example: 12 | 13 | ```clojure 14 | (ns my.cool.project-test 15 | (:require [expectations.clojure.test 16 | :refer [defexpect expect ,,, use-fixtures]])) 17 | ``` 18 | 19 | You then define your fixture as a function that accepts the test(s) to be run as a single argument, performs whatever setup you need, calls the test(s), and the performs whatever teardown you need. Since tests could throw exceptions, you generally want to use `try`/`finally` here to ensure teardown runs even if the tests abort: 20 | 21 | ```clojure 22 | (defn my-fixture [work] 23 | ;; perform test setup 24 | (try 25 | (work) 26 | (finally 27 | ;; perform test teardown 28 | ))) 29 | 30 | (defn setup [] (,,,)) 31 | 32 | (defn teardown [] (,,,)) 33 | ``` 34 | 35 | Then you inform `clojure.test` about your fixture, telling it to run around each test in this namespace, or just once around the whole namespace of tests: 36 | 37 | ```clojure 38 | ;; as a top-level form, usually before you define your tests: 39 | (use-fixtures :each my-fixture) ; run around each test 40 | ;; or 41 | (use-fixtures :each {:before setup, :after teardown}) ; run around each test 42 | ;; or 43 | (use-fixtures :once my-fixture) ; run once around the whole namespace 44 | (use-fixtures :once {:before setup, :after teardown}) ; run once around the whole namespace 45 | ``` 46 | 47 | `use-fixtures` can accept multiple fixtures if you need to combine setup and/or teardown from more than one test context. 48 | 49 | Here's an example that sets up a database connection pool for use across the whole namespace and sets up a database connection for use in each test: 50 | 51 | ```clojure 52 | (ns my.cool.project-test 53 | (:require [expectations.clojure.test 54 | :refer [defexpect expect in ,,, use-fixtures]] 55 | [next.jdbc :as jdbc] 56 | [next.jdbc.connection :as connection] 57 | [project.membership :as sut]) 58 | (:import (com.zaxxer.hikari HikariDataSource))) 59 | 60 | (def ^:dynamic *pool* nil) 61 | (def ^:dynamic *con* nil) 62 | 63 | (def db-spec {:dbtype "..." :dbname "testdb" ,,,}) 64 | 65 | (defn pool-setup [work] 66 | (let [pool (connection/->pool HikariDataSource db-spec)] 67 | (try 68 | (binding [*pool* pool] 69 | (work)) 70 | (finally 71 | (.close pool))))) 72 | 73 | (defn connection-setup [work] 74 | (with-open [con (jdbc/get-connection *pool*)] 75 | (binding [*con* con] 76 | (work)))) 77 | 78 | (use-fixtures :once pool-setup) 79 | (use-fixtures :each connection-setup) 80 | 81 | (def test-user {,,,}) 82 | 83 | (defexpect db-test 84 | (expect {:membership/status "active"} 85 | (in (sut/lookup-membership *con* test-user)))) 86 | 87 | ;; lots more tests that use *con* 88 | ``` 89 | 90 | When the tests in this namespace are run, the underlying `clojure.test` machinery 91 | will invoke `pool-setup` once, passing in a function that will invoke 92 | `connection-setup` for each test in the namespace, 93 | in turn passing in that test (as a function). 94 | 95 | > Note: Fixtures are only executed when tests are run via `clojure.test/run-tests` or `clojure.test/test-vars` -- just invoking a test as a function, e.g., `(db-test)` will not cause the fixtures to run. As of 2.0.0, these are available via `expectations.clojure.test` directly, without requiring `clojure.test`. 96 | 97 | ## Focused Test Execution 98 | 99 | Most test runners allow you to specify namespace(s) or patterns to match namespaces so that you can restrict your test run to just some namespaces in your project rather than all of them. 100 | 101 | ```bash 102 | > lein test just.this.ns-test 103 | ... 104 | > boot test -n just.this.ns-test 105 | ... 106 | > clojure -X:test :nses '[just.this.ns-test]' 107 | ... 108 | ``` 109 | 110 | Some runners let you specify a regex to match on the namespaces to run: 111 | 112 | ```bash 113 | > boot test -I "just.*-test$" 114 | ... 115 | > clojure -X:test :patterns '["just.*-test$"]' 116 | ... 117 | ``` 118 | 119 | > `boot` also lets you use a regex to exclude namespaces via `-X` 120 | 121 | Some runners let you run a specific test: 122 | 123 | ```bash 124 | > lein test :only just.this.ns-test/just-this-test 125 | ... 126 | > clojure -X:test :vars '[just.this.ns-test/just-this-test]' 127 | ... 128 | ``` 129 | 130 | It is also very useful to be able to tag individual tests with metadata and then include or exclude groups of tests when you run them. Both Leiningen and Cognitect's `test-runner` support this using simple keyword metadata on test functions. 131 | 132 | The common example given for this is to mark tests as being "integration" level (rather than "unit" level) so they might only be run in a full continuous integration test suite pass, whereas you might run just the lightweight "unit" tests all the time locally while developing -- the assumption being that integration tests are slow and/or have complex environment setup/teardown requirements. 133 | 134 | The test suite for this library annotates some of the negative tests so that they can be excluded when running the tests with Humane Test Output enabled (since that modifies the test report data structure in ways that can be incompatible with the `is-not'` macro used to verify failing tests are reported): 135 | 136 | ```clojure 137 | (deftest ^:negative not-collection-test 138 | (is-not' (sut/expect {:foo 1} (in {:foo 2 :cat 4})) (not (=? {:foo 1} {:foo 2})))) 139 | ``` 140 | 141 | `^:negative` is a simple piece of metadata added to `not-collection-test` so that tests can be run like this: 142 | 143 | ```bash 144 | > clojure -X:test:humane :excludes '[:negative]' 145 | ``` 146 | 147 | By contrast, this runs all the tests (without Humane Test Output enabled): 148 | 149 | ```bash 150 | > clojure -X:test 151 | ``` 152 | 153 | Cognitect's `test-runner` also has an `:includes` option to include only tests marked with specific metadata: 154 | 155 | ```bash 156 | > clojure -X:test :includes '[:negative]' 157 | ``` 158 | 159 | This runs _only_ tests marked as being `^:negative`. 160 | 161 | Leiningen's approach uses an additional layer in its `project.clj` file where you specify `:test-selectors` which are labels for predicates that run on the metadata of tests to determine whether to include them or not. Run `lein test help` for details. The equivalent of the above "humane" run would be: 162 | 163 | ```clojure 164 | :profiles {:humane {:dependencies [pjstadig/humane-test-output "0.11.0"]}} 165 | :test-selectors {:humane (complement :negative)} 166 | ``` 167 | 168 | and: 169 | 170 | ```bash 171 | > lein with-profile humane test :humane 172 | ``` 173 | 174 | > `with-profile humane` is equivalent to `-A:humane` in the CLI above and `lein ... test :humane` is equivalent to `-e :negative` because it has `(complement :negative)` in the test selector definition. 175 | 176 | # Further Reading 177 | 178 | * [Getting Started](/doc/getting-started.md) 179 | * [Useful Predicates](/doc/useful-predicates.md) 180 | * [Collections](/doc/collections.md) 181 | * [Expecting More](/doc/more.md) 182 | * [Expecting Side Effects](/doc/side-effects.md) 183 | -------------------------------------------------------------------------------- /doc/getting-started-cljs.md: -------------------------------------------------------------------------------- 1 | # Getting Started with expectations/clojure-test using ClojureScript 2 | 3 | > NOTE: ClojureScript support, via `planck` is coming in 2.0.0 but you can try it out now via the **develop** branch in the repo! 4 | 5 | You can use `expectations/clojure-test` to run tests in both Clojure 6 | and ClojureScript. Many tests will work without changes in both 7 | Clojure and ClojureScript, though of course some will require 8 | changes for the different environments. This section describes how 9 | to use `expectations/clojure-test` in ClojureScript and the differences 10 | from using it in Clojure -- see the other sections for details of how 11 | to use it in Clojure for a complete picture. 12 | 13 | 14 | ## Installation 15 | 16 | In order to run `expectations/clojure-test` with ClojureScript, you 17 | will use `olical/cljs-test-runner` and the Clojure tool `clj`. 18 | 19 | Your `deps.edn` should include this information: 20 | 21 | ```clojure 22 | {:aliases {:cljs-runner 23 | {:extra-deps {com.github.seancorfield/expectations {:mvn/version "2.2.214"}, 24 | olical/cljs-test-runner {:mvn/version "3.8.1"}, 25 | pjstadig/humane-test-output {:mvn/version "0.11.0"}}, 26 | :extra-paths ["src" "test" "cljs-test-runner-out/gen"], 27 | :main-opts ["-m" "cljs-test-runner.main" 28 | "--doo-opts" "dooopts.edn" 29 | "-x" "planck"]}}} 30 | ``` 31 | 32 | You will need two small `.edn` files in your project: 33 | 34 | `dooopts.edn`: 35 | ```clojure 36 | {:paths {:planck "planck --compile-opts planckopts.edn"}} 37 | ``` 38 | 39 | `planckopts.edn`: 40 | ```clojure 41 | {:warnings {:private-var-access false}} 42 | ``` 43 | 44 | To run the tests, you run: 45 | 46 | ``` 47 | clj -M:cljs-runner 48 | ``` 49 | 50 | These tests will take a good while longer to run than the same tests 51 | in Clojure, so if you don't get any output for a while, that is not 52 | necessarily a bad thing. 53 | 54 | ### Requirements 55 | 56 | The ClojureScript version of `expectations/clojure-test` works (at present) 57 | only with a specific implementation of self-hosted ClojureScript: 58 | [`planck`](https://planck-repl.org). You will have to install `planck` 59 | yourself in order to use `expectations/clojure-test` with ClojureScript. 60 | 61 | You will have to get `planck -h` to work locally. See 62 | [here](https://planck-repl.org) for instructions on how to install 63 | `planck` on a variety of systems. Planck `2.24.0` or later is required. 64 | 65 | ### Humane Test Output 66 | 67 | The use of Paul Stadig's 68 | [Humane Test Output](https://github.com/pjstadig/humane-test-output), is 69 | optional for the Clojure version of `expectations/clojure-test` but it is 70 | required for the ClojureScript version of `expectations/clojure-test`. 71 | 72 | ## The Basics 73 | 74 | This example is the ClojureScript version of the quick comparison provided 75 | for the Clojure version of `expectations/clojure-test`, and provides a quick 76 | comparison with `clojure.test` (the tests match those in the [`clojure.test` 77 | documentation](http://clojure.github.io/clojure/clojure.test-api.html)): 78 | 79 | ```clojure 80 | (require '[expectations.clojure.test :refer [defexpect expect expecting]]) 81 | 82 | (defexpect simple-test ; (deftest simple-test 83 | (expect 4 (+ 2 2)) ; (is (= 4 (+ 2 2))) 84 | (expect number? 256) ; (is (instance? Long 256)) 85 | (expect (.startsWith "abcde" "ab")) ; (is (.startsWith "abcde" "ab")) 86 | (expect ##Inf (/ 1 0)) ; (is (thrown? ArithmeticException (/ 1 0))) 87 | (expecting "Arithmetic" ; (testing "Arithmetic" 88 | (expecting "with positive integers" ; (testing "with positive integers" 89 | (expect 5 (+ 2 2)) ; (is (= 4 (+ 2 2))) 90 | (expect 7 (+ 3 4))) ; (is (= 7 (+ 3 4)))) 91 | (expecting "with negative integers" ; (testing "with negative integers" 92 | (expect -4 (+ -2 -2)) ; (is (= -4 (+ -2 -2))) 93 | (expect -1 (+ 3 -4))))) ; (is (= -1 (+ 3 -4)))))) 94 | ``` 95 | 96 | The third example could also be written as follows, since `expect` 97 | allows an arbitrary predicate in the "expected" position: 98 | 99 | ```clojure 100 | (expect #(.startsWith % "ab") "abcde") 101 | ``` 102 | 103 | Or like this, since `expect` allows a regular expression in the "expected" position: 104 | 105 | ```clojure 106 | (expect #"^ab" "abcde") 107 | ``` 108 | 109 | Both of these more accurately reflect an expectation on the actual 110 | value `"abcde"`, that the string begins with `"ab"`, than the `is` 111 | equivalent which has the actual value embedded in the test expression. 112 | Separating the "expectation" (value or predicate) from the "actual" 113 | expression being tested often makes the test much clearer. 114 | 115 | ## Differences from the Clojure version of `expectations/clojure-test` 116 | 117 | Here is the list of features from Expectations supported by the 118 | Clojure version of `expectations.clojure.test` where there are 119 | differences in the ClojureScript implementation. 120 | 121 | ### * Class test 122 | 123 | Classes are all different in ClojureScript, and in some cases things 124 | that would be a class in Clojure are different in ClojureScript. For 125 | instance, lists are a class: 126 | ```clojure 127 | (defexpect class-test (expect cljs.core/List '(a b c))) 128 | ``` 129 | and this test passes. Strings, however, don't have an easily 130 | discoverable type or class, and are better handled with a predicate: 131 | ```clojure 132 | (defexpect string-class-test (expect string? "abc")) 133 | ``` 134 | In general, the classes in ClojureScript will not be the same as 135 | the classes in Clojure. You can do this to write a test that 136 | will work in both environments: 137 | ```clojure 138 | (defexpect both-class-test (expect (= (type "abc") (type "def")))) 139 | ``` 140 | but you cannot write this: 141 | ``` 142 | (defexpect bad-both-class-test (expect (type "abc") (type "def"))) 143 | ``` 144 | because `(type "abc")` yields something that tests positive as a 145 | `fn?`, causing expectations to think it is a predicate. Which, 146 | as it happens, it is not. 147 | 148 | ### * Exception test 149 | 150 | Exceptions are very different in ClojureScript from Clojure. 151 | 152 | The Clojure example: 153 | ```clojure 154 | (defexpect divide-by-zero (expect ArithmeticException (/ 12 0))) 155 | ``` 156 | doesn't even throw an exception -- it returns `##Inf`. 157 | You can do this for that situation: 158 | ```clojure 159 | (defexpect divide-by-zero (expect ##Inf (/ 12 0))) 160 | ``` 161 | but be careful putting `##Inf` in a reader conditional, as some versions of 162 | Clojure don't handle that well. But all of this is a bit off-topic, 163 | as we are discussing exceptions. 164 | 165 | Exceptions certainly exist and can be thrown. You can throw pretty 166 | much anything in Javascript. There is no `Throwable` class in 167 | Clojurecript to distinguish things that can be thrown from anything 168 | else. The only exception supported in `expectations/clojure-test` 169 | in ClojureScript is where the exception is: `js/Error`. For example: 170 | ```clojure 171 | (defexpect exception (expect js/Error (count 5))) 172 | ``` 173 | will pass, because `(count 5)` throws `js/Error`. 174 | 175 | ### * `with-test` 176 | There is no `with-test` in `cljs.test`, so it is not available in 177 | `expectations/clojure-test`. 178 | 179 | ### * Specs 180 | Specs are always supported, and work equivalently to Clojure. 181 | 182 | # Useful Additional Information 183 | 184 | The end of the Clojure [Getting Started](/doc/getting-started.md) provides 185 | additional information on how to use `expectations/clojure-test`, and most 186 | of the information is directly applicable to using `expectations/clojure-test` 187 | in ClojureScript as well. 188 | 189 | # Further Reading 190 | 191 | Expectations provides a lot more: 192 | 193 | * [Useful Predicates](/doc/useful-predicates.md) 194 | * [Collections](/doc/collections.md) 195 | * [Expecting More](/doc/more.md) 196 | * [Expecting Side Effects](/doc/side-effects.md) 197 | * [Fixtures & Focused Test Execution](/doc/fixtures-focus.md) 198 | -------------------------------------------------------------------------------- /doc/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started with expectations.clojure.test 2 | 3 | This library provides an expressive alternative to [`clojure.test`](http://clojure.github.io/clojure/clojure.test-api.html), based on the syntax of Jay Fields' [Expectations](https://clojure-expectations.github.io/) library, but fully compatible with all the `clojure.test`-based tooling out there: no special plugins or editor modes are needed. 4 | 5 | ## Installation 6 | 7 | You can add `expectations.clojure.test` to your project with either: 8 | 9 | ```clojure 10 | ;; add this to :extra-deps under a :test alias: 11 | com.github.seancorfield/expectations {:mvn/version "2.2.214"} 12 | ``` 13 | for `deps.edn` or: 14 | 15 | ```clojure 16 | ;; add this to :dev-dependencies (Leiningen) 17 | [com.github.seancorfield/expectations "2.2.214"] 18 | ;; or add this to :dependencies (Boot) 19 | [com.github.seancorfield/expectations "2.2.214" :scope "test"] 20 | ``` 21 | for `project.clj` or `build.boot`. 22 | 23 | Then in your test namespaces, you just require `expectations.clojure.test` (instead of `clojure.test`) and start using the Expectations-style syntax for your tests. 24 | 25 | ### Requirements 26 | 27 | This library is designed to work with Clojure 1.9 or later, and provides support for `clojure.spec` predicates. It is also designed to work with Paul Stadig's [Humane Test Output](https://github.com/pjstadig/humane-test-output), which provides better failure messages for `clojure.test`. 28 | 29 | ### Humane Test Output 30 | 31 | If you have `pjstadig/humane-test-output` as a dependency (i.e., it is on your classpath), then when you require `expectations.clojure.test` it will automatically activate Humane Test Output, regardless of how you are running your tests: again, no need for any special setup or `:injections` (Leiningen). 32 | 33 | > Take note of the caveat Paul Stadig provides about some tooling and/or IDEs installing their own "helpers" for `clojure.test` output! 34 | 35 | ## The Basics 36 | 37 | This example provides a quick comparison with `clojure.test` (the tests match those in the [`clojure.test` documentation](http://clojure.github.io/clojure/clojure.test-api.html)): 38 | 39 | ```clojure 40 | (require '[expectations.clojure.test 41 | :refer [defexpect expect expecting run-tests test-vars]]) 42 | 43 | (defexpect simple-test ; (deftest simple-test 44 | (expect 4 (+ 2 2)) ; (is (= 4 (+ 2 2))) 45 | (expect Long 256) ; (is (instance? Long 256)) 46 | (expect (.startsWith "abcde" "ab")) ; (is (.startsWith "abcde" "ab")) 47 | (expect ArithmeticException (/ 1 0)) ; (is (thrown? ArithmeticException (/ 1 0))) 48 | (expecting "Arithmetic" ; (testing "Arithmetic" 49 | (expecting "with positive integers" ; (testing "with positive integers" 50 | (expect 4 (+ 2 2)) ; (is (= 4 (+ 2 2))) 51 | (expect 7 (+ 3 4))) ; (is (= 7 (+ 3 4)))) 52 | (expecting "with negative integers" ; (testing "with negative integers" 53 | (expect -4 (+ -2 -2)) ; (is (= -4 (+ -2 -2))) 54 | (expect -1 (+ 3 -4))))) ; (is (= -1 (+ 3 -4)))))) 55 | ``` 56 | 57 | The third example could also be written as follows, since `expect` allows an arbitrary predicate in the "expected" position: 58 | 59 | ```clojure 60 | (expect #(.startsWith % "ab") "abcde") 61 | ``` 62 | 63 | Or like this, since `expect` allows a regular expression in the "expected" position: 64 | 65 | ```clojure 66 | (expect #"^ab" "abcde") 67 | ``` 68 | 69 | Both of these more accurately reflect an expectation on the actual value `"abcde"`, that the string begins with `"ab"`, than the `is` equivalent which has the actual value embedded in the test expression. Separating the "expectation" (value or predicate) from the "actual" expression being tested often makes the test much clearer. 70 | 71 | ## Running Tests 72 | 73 | How you run tests will depend a lot on the tooling and/or IDE/editor that you use in your day-to-day workflow. 74 | 75 | ### Editor Integration 76 | 77 | While you are developing tests, it's probably best to run them via your editor (using the REPL connected to it). Most Clojure integrations for editors allow you run an individual test, all tests in a given namespace, or all tests in the project. You'll have to consult the documentation for your chosen editor/integration for the ways to do that. 78 | 79 | ### REPL 80 | 81 | If you are working directly in the REPL (not recommended but, hey...) you can run an individual test simply by calling it, as if it were a function: 82 | 83 | ```clojure 84 | user=> (simple-test) 85 | nil 86 | ``` 87 | 88 | It will return `nil` and print nothing if the test succeeds. It will print out failure messages otherwise (and still return `nil`). While this is the simplest way to run a test, it is not always the best, since it won't run any test fixtures -- see [Fixtures](/doc/fixtures-focus.md) for more details. You can run a test (with fixtures) like this: 89 | 90 | ```clojure 91 | user=> (test-vars [#'simple-test]) 92 | nil 93 | ``` 94 | 95 | As you might imagine, you can run more than one test using `test-vars`. You can also run all the tests in the current namespace, which produces more informative output: 96 | 97 | ```clojure 98 | user=> (run-tests) 99 | 100 | Testing user 101 | 102 | Ran 1 tests containing 8 assertions. 103 | 0 failures, 0 errors. 104 | {:test 1, :pass 8, :fail 0, :error 0, :type :summary} 105 | ``` 106 | 107 | As of 2.0.0, `test-vars` and `run-tests` are imported from `clojure.test` automatically behind the scenes, along with other test running functions. 108 | 109 | ### Cognitect's `test-runner` 110 | 111 | This assumes you are using the [CLI and `deps.edn`](https://clojure.org/guides/deps_and_cli) for your project, and that you have set up a `:test` alias per [`test-runner`'s README](https://github.com/cognitect-labs/test-runner/blob/master/readme.md): 112 | 113 | ```bash 114 | > clojure -X:test 115 | ``` 116 | 117 | ### Leiningen 118 | 119 | The following is usually sufficient to run tests via Leiningen, assuming your `project.clj` file is set up correctly: 120 | 121 | ```bash 122 | > lein test 123 | ``` 124 | 125 | ### Boot 126 | 127 | The following is usually sufficient to run tests via Boot, assuming your `build.boot` file is set up correctly (including [Adzerk's `boot-test`](https://github.com/adzerk-oss/boot-test)): 128 | 129 | ```bash 130 | > boot test 131 | ``` 132 | 133 | ### Test Placement 134 | 135 | While not directly related to how to run your tests, it's a common question asked by folks new to Clojure: where should I put my tests? 136 | 137 | #### Standard Conventions 138 | 139 | Most of the `clojure.test`-based tooling assumes that for each source file `src/path/to/my_code.clj` (which represents the namespace `path.to.my-code`), you will have a test file `test/path/to/my_code_test.clj` with the namespace `path.to.my-code-test`. 140 | 141 | That test file will generally start out with: 142 | 143 | ```clojure 144 | (ns path.to.my-code-test 145 | (:require [expectations.clojure.test :refer [defexpect expect expecting ,,,] 146 | [path.to.my-code :refer [the-functions you-want to-test]]])) 147 | ``` 148 | 149 | Following this convention means that all the tooling and IDE/editor integrations should work with no configuration: it's what everyone "expects". 150 | 151 | #### Tests with Source Code 152 | 153 | `clojure.test` has a macro called `with-test` that allows you to define tests inline following your function definition. Given that `clojure.test` ships directly with Clojure, this is reasonable because putting test code in your function definition's metadata doesn't add any dependencies and it has the benefit of being able to see the source of the function and the source of its test right next to each other. You can do that with Expectations too, since it is `clojure.test`-compatible, although it does mean your source code has an additional dependency -- but Expectations is fairly small (~300 lines) and has no additional dependencies. As of 2.0.0, `with-test` is available directly in `expectations.clojure.test`. 154 | 155 | However, if you put tests in your source files, using `with-test`, then most tooling won't know how to find those tests by default. Here's an example of an inline test and how to run it with Leiningen and the CLI (`deps.edn`): 156 | 157 | ```clojure 158 | (ns my.cool.project 159 | (:require [expectations.clojure.test :refer [expect with-test]])) 160 | 161 | (with-test 162 | (defn square [x] (* x x)) 163 | (expect 1 (square 1)) 164 | (expect 1 (square -1)) 165 | (expect 100 (square 10))) 166 | ``` 167 | 168 | For Leiningen, you'll need to tell it to look for tests in `src` (as well as `test`) so add this to `project.clj`: 169 | 170 | ```clojure 171 | :test-paths ["src" "test"] 172 | ``` 173 | 174 | then you can just run `lein test` and it will check for tests inside the `src` test, find `my.cool.project/square` test metadata and run it as a test. 175 | 176 | For the `clojure` CLI, you'll need to tell Cognitect's `test-runner` to look for tests in `src` _and_ you'll have to override it's default regex pattern for matching test namespaces: 177 | 178 | ```bash 179 | clojure -X:test :dirs '["src"]' :patterns '[".*"]' 180 | ``` 181 | 182 | Of course, you can also update the `:test` alias to add those new options into `:exec-args` so that you don't need them on the command line: 183 | 184 | ```clojure 185 | {:aliases 186 | {:test 187 | {:extra-paths ["test"] 188 | :extra-deps 189 | {com.github.seancorfield/expectations {:mvn/version "2.2.214"} 190 | ;; assumes Clojure CLI 1.10.3.933 or later: 191 | io.github.cognitect-labs/test-runner 192 | {:git/tag "v0.5.0" :git/sha "48c3c67"}} 193 | :exec-fn cognitect.test-runner.api/test 194 | :exec-args {:dirs ["src" "test"] 195 | :patterns [".*"]}}}} 196 | ``` 197 | 198 | Note that you'll need both `src` _and_ `test` directories if you want `test-runner` to look in both places. 199 | 200 | ## Expecting Specs 201 | 202 | If you are using Clojure 1.9 or later, you have access to Spec and can `expect` those as well: 203 | 204 | ```clojure 205 | (require '[clojure.spec.alpha :as s]) 206 | (s/def :small/value (s/and pos-int? #(< % 100))) 207 | (defexpect spec-test 208 | (expect :small/value (* 14 3))) 209 | ``` 210 | 211 | If an expectation on a Spec fails, you get the explanation as well as the standard `clojure.test` failure: 212 | 213 | ```clojure 214 | (defexpect spec-failure 215 | (expect :small/value (* 14 30))) 216 | 217 | ;; when run: 218 | 219 | FAIL in (spec-failure) (...:...) 220 | (* 14 30) 221 | 222 | val: 420 fails spec: :small/value predicate: (< % 100) 223 | 224 | expected: (s/valid? :small/value (* 14 30)) 225 | actual: (not (s/valid? :small/value 420)) 226 | ``` 227 | 228 | ## Failure Messages 229 | 230 | Just like the `is` macro, the `expect` macro can take an additional (third) argument that is a message to display if the expectation fails: 231 | 232 | ```clojure 233 | user=> (defexpect failure-msg 234 | (expect even? (+ 1 1 1) "It's uneven!")) 235 | #'user/failure-msg 236 | user=> (failure-msg) 237 | 238 | FAIL in (failure-msg) (...:...) 239 | It's uneven! 240 | (+ 1 1 1) 241 | 242 | (+ 1 1 1) did not satisfy even? 243 | 244 | expected: (even? (+ 1 1 1)) 245 | actual: (not (even? 3)) 246 | nil 247 | 248 | ;; messages are combined in a Spec failure: 249 | 250 | user=> (defexpect spec-failure-msg 251 | (expect :small/value (* 14 30) "Too big!")) 252 | #'user/spec-failure-msg 253 | user=> (spec-failure-msg) 254 | 255 | FAIL in (spec-failure) (...:...) 256 | Too big! 257 | (* 14 30) 258 | 259 | val: 420 fails spec: :small/value predicate: (< % 100) 260 | 261 | expected: (s/valid? :small/value (* 14 30)) 262 | actual: (not (s/valid? :small/value 420)) 263 | nil 264 | 265 | ;; expecting adds its message too: 266 | 267 | user=> (defexpect another-spec-failure-msg 268 | (expecting "Large number should fail" 269 | (expect :small/value (* 14 30) "Too big!")) 270 | (expecting "Negative number should fail" 271 | (expect :small/value (* -14 30) "Too small!"))) 272 | #'user/another-spec-failure-msg 273 | user=> (another-spec-failure-msg) 274 | 275 | FAIL in (another-spec-failure-msg) (...:...) 276 | Large number should fail 277 | Too big! 278 | (* 14 30) 279 | 280 | val: 420 fails spec: :small/value predicate: (< % 100) 281 | 282 | expected: (s/valid? :small/value (* 14 30)) 283 | actual: (not (s/valid? :small/value 420)) 284 | 285 | FAIL in (another-spec-failure-msg) (...:...) 286 | Negative number should fail 287 | Too small! 288 | (* -14 30) 289 | 290 | val: -420 fails spec: :small/value predicate: pos-int? 291 | 292 | expected: (s/valid? :small/value (* -14 30)) 293 | actual: (not (s/valid? :small/value -420)) 294 | nil 295 | ``` 296 | 297 | # Further Reading 298 | 299 | While the above can already get you further than `clojure.test`, Expectations provides a lot more: 300 | 301 | * [Useful Predicates](/doc/useful-predicates.md) 302 | * [Collections](/doc/collections.md) 303 | * [Expecting More](/doc/more.md) 304 | * [Expecting Side Effects](/doc/side-effects.md) 305 | * [Fixtures & Focused Test Execution](/doc/fixtures-focus.md) 306 | -------------------------------------------------------------------------------- /doc/more.md: -------------------------------------------------------------------------------- 1 | # Expecting More 2 | 3 | So far we've seen expectations with just a single value or predicate being used to test one or more result values. We will often want to expect several things about our results, and Expectations provides a way to articulate that without writing multiple checks that duplicate the expression being tested. 4 | 5 | ## `expect more` 6 | 7 | If you have multiple predicates that you expect to be satisfied by a given expression, you can use `more` to combine them into a single expectation: 8 | 9 | ```clojure 10 | (expect (more vector? not-empty) [1 2 3]) 11 | ``` 12 | 13 | This expects the (actual) test value to be a vector and also to be non-empty (we could have specified `seq` there just as easily). This can be particularly powerful when combined with `from-each` to check that multiple expectations hold for computations applied to multiple input values: 14 | 15 | ```clojure 16 | (expect (more vector? not-empty) 17 | (from-each [n [1 2 3]] 18 | (into [] (range n)))) 19 | ``` 20 | 21 | > If you have expectations that should hold for **all** input values, you might want to look at [`clojure.test.check`](https://github.com/clojure/test.check) instead. 22 | 23 | ## `expect more->` 24 | 25 | If you have a series of predicates (or values) that you expect to be satisfied by a given expression after a certain amount of preprocessing, you can use the threaded version -- `more->` -- to express that: 26 | 27 | ```clojure 28 | (expect (more-> 1 first 29 | 3 last) 30 | [1 2 3]) 31 | ``` 32 | 33 | `more->` accepts a series of predicate (or value) and expression pairs. 34 | The (actual) test value is threaded through each of the expressions and the predicate (or value) is expected of the result. 35 | 36 | The example above is equivalent to these two expectations: 37 | 38 | ```clojure 39 | (expect 1 (-> [1 2 3] first)) 40 | (expect 3 (-> [1 2 3] last)) 41 | ``` 42 | 43 | Going back to our `lookup-membership` example (in 44 | [**Collections**](/doc/collections.md) and 45 | [**Fixtures**](/doc/fixtures-focus.md)), we might want to expect: 46 | 47 | ```clojure 48 | (expect (more-> #{:membership/id :membership/status :membership/type ,,,} 49 | (-> keys set) 50 | pos? :membership/id 51 | "active" :membership/status 52 | "platinum" :membership/type) 53 | (lookup-membership db-spec test-user)) 54 | ``` 55 | 56 | Since the test value is threaded-first into the expressions, we can use `->` to further thread the value into additional processing steps, such as getting the sequence of `keys` and turning that into a `set` for the expectation of which keys should be returned in a membership hash map. In addition, we expect that `(-> actual :membership/id)` is positive `pos?` and that certain other keys have specific values. 57 | 58 | `clojure.test` provides `thrown-with-msg?` as a way to assert both the type of exception thrown and a regular expression that should apply to the message in that exception. `more->` allows us to do that in a more general way: 59 | 60 | ```clojure 61 | (is (thrown-with-msg? ArithmeticException #"Divide by zero" (/ 1 0))) 62 | (expect (more-> ArithmeticException type 63 | #"Divide by zero" ex-message) 64 | (/ 1 0)) 65 | ``` 66 | 67 | See below for a more comprehensive example of exception testing that also uses `more-of`. 68 | 69 | ## `expect more-of` 70 | 71 | Sometimes destructuring an (actual) test value is the easiest way to apply your expectations: 72 | 73 | ```clojure 74 | (expect (more-of {:membership/keys [id status type] :as data} 75 | ,,, 76 | pos? id 77 | "active" status 78 | "platinum" type) 79 | (lookup-membership db-spec test-user)) 80 | ``` 81 | 82 | The expectation on the set of keys has been omitted here to highlight how the destructuring may simplify the other expectations, but it would be: 83 | 84 | ```clojure 85 | #{:membership/id :membership/status :membership/type ,,,} 86 | (set (keys data)) 87 | ``` 88 | 89 | Some simpler examples (taken from Expectations' original documentation): 90 | 91 | ```clojure 92 | (expect (more-of x 93 | vector? x 94 | 1 (first x)) 95 | [1 2 3]) 96 | (expect (more-of [x :as all] 97 | vector? all 98 | 1 x) 99 | [1 2 3]) 100 | ``` 101 | 102 | You can think of `more-of` as being a shorthand for a predicate function 103 | that `expect`s the pairs in its body: 104 | 105 | ```clojure 106 | (expect (fn [x] 107 | (expect vector? x) 108 | (expect 1 (first x))) 109 | [1 2 3]) 110 | (expect (fn [[x :as all]] 111 | (expect vector? all) 112 | (expect 1 x)) 113 | [1 2 3]) 114 | ``` 115 | 116 | `more-of` can be used with `from-each` to provide functionality similar 117 | to `are` in `clojure.test` (but more powerful): 118 | 119 | ```clojure 120 | (deftest are-example 121 | (are [expected start end] 122 | (= expected (range start end)) 123 | [0 1 2 3] 0 4 124 | [] 0 0 125 | [1 2 3] 1 4)) 126 | 127 | (defexpect equivalent-to-are 128 | (expect (more-of [expected actual] 129 | expected actual) 130 | (from-each [[expected start end] 131 | [[[0 1 2 3] 0 4] 132 | [[] 0 0] 133 | [[1 2 3] 1 4]]] 134 | [expected (range start end)]))) 135 | ``` 136 | 137 | Although this is more verbose for this basic example, remember that the 138 | `expected` value could also be a predicate function, a regex, a Spec, etc: 139 | 140 | ```clojure 141 | (s/def ::coll-of-ints (s/coll-of int?)) 142 | 143 | (defexpect more-than-are 144 | (expect (more-of [expected actual] 145 | ::coll-of-ints actual 146 | expected actual) 147 | (from-each [[expected start end] 148 | [[[0 1 2 3] 0 4] 149 | [empty? 0 0] 150 | [[1 2 3] 1 4]]] 151 | [expected (range start end)]))) 152 | ``` 153 | 154 | > Note: a Spec is only recognized as literal keyword in the "expected" position so it has to be directly in `more-of` rather than passed via `from-each`. This restriction will probably be lifted in a future release. 155 | 156 | `more-of` can also be used with `more->` to provide succinct tests on Clojure's `ex-info` exceptions: 157 | 158 | ```clojure 159 | (defexpect ex-info-tests 160 | (expect (more-> clojure.lang.ExceptionInfo type 161 | #"boo" ex-message 162 | (more-of {:keys [status responseCode]} 163 | 409 status 164 | 4001110 responseCode) ex-data) 165 | (throw (ex-info "boo" {:status 409 :responseCode 4001110})))) 166 | ``` 167 | 168 | In this example, the exception is threaded into `type` and the predicate is a class, 169 | it is threaded into `ex-message` and the predicate is a regex, 170 | and it is also threaded into `ex-data` and the predicate is a `more-of` expression that destructures that data and matches parts of it. 171 | 172 | Another good use of `more-of` is for expectations on 173 | [**Side Effects**](/doc/side-effects.md). 174 | 175 | # Further Reading 176 | 177 | * [Getting Started](/doc/getting-started.md) 178 | * [Useful Predicates](/doc/useful-predicates.md) 179 | * [Collections](/doc/collections.md) 180 | * [Expecting Side Effects](/doc/side-effects.md) 181 | * [Fixtures & Focused Test Execution](/doc/fixtures-focus.md) 182 | -------------------------------------------------------------------------------- /doc/side-effects.md: -------------------------------------------------------------------------------- 1 | # Expecting Side Effects 2 | 3 | Sometimes the code we are testing calls out to other functions to cause side effects. 4 | When we are testing, we don't always want side effects to happen but we often want 5 | to make sure the code being tested still calls those functions. 6 | 7 | Expectations provides a `side-effects` macro that lets you run the code under 8 | test with those side-effecting functions mocked out, and returns a vector of 9 | all of the argument lists passed in calls to those functions. 10 | 11 | ```clojure 12 | (defn my-fn [x] (cond x (println "x" x) 13 | (nil? x) (println "no value"))) 14 | 15 | (defexpect my-fn-test 16 | (expect [["x" 42]] 17 | (side-effects [println] (my-fn 42))) 18 | (expect [["no value"]] 19 | (side-effects [println] (my-fn nil))) 20 | (expect empty? 21 | (side-effects [println] (my-fn false)))) 22 | ``` 23 | 24 | Our function under test calls `println` conditionally. We want to verify the logic 25 | so we expect `println` to be called with `"x"` and the (truthy) value passed to `my-fn`, 26 | `"no value"` when `nil` is passed to `my-fn`, and for there to be no calls to 27 | `println` if other values are passed (i.e., if `false` is passed). 28 | 29 | By default, mocked out calls return `nil`, but sometimes your code under test 30 | will expect other values back, in order for you to correctly test paths through 31 | that code. You can specify the mocked function as a pair of its name and its 32 | return value in that case. 33 | 34 | ```clojure 35 | (defn my-pred [x] (= 42 x)) 36 | (defn my-code [x] (if (my-pred x) (println "good") (println "bad"))) 37 | 38 | (defexpect my-code-test-1 39 | (expect [[99] ["bad"]] 40 | (side-effects [my-pred println] (my-code 99))) 41 | ;; this will fail because the mocked my-pred returns nil 42 | (expect [[42] ["good"]] 43 | (side-effects [my-pred println] (my-code 42)))) 44 | 45 | (defexpect my-code-test-2 46 | (expect [[99] ["bad"]] 47 | (side-effects [my-pred println] (my-code 99))) 48 | (expect [[42] ["good"]] 49 | (side-effects [[my-pred true] println] (my-code 42)))) 50 | ``` 51 | 52 | Here's the output of running those two tests: 53 | 54 | ```clojure 55 | user=> (my-code-test-1) 56 | 57 | FAIL in (my-code-test-1) (...:...) 58 | (side-effects [my-pred println] (my-code 42)) 59 | 60 | expected: (= [[42] ["good"]] (side-effects [my-pred println] (my-code 42))) 61 | actual: (not= [[42] ["good"]] [(42) ("bad")]) 62 | nil 63 | user=> (my-code-test-2) 64 | nil 65 | ``` 66 | 67 | As we can see, the second expectation in `my-code-test-1` fails because `my-code` 68 | calls `println` with `"bad"` -- because the mocked `my-pred` returns `nil` and 69 | so `my-code` executes the non-truthy path. 70 | 71 | In `my-code-test-2`, we mock `my-pred` to return `true` and so `my-code` executes 72 | the truthy path and we can verify that calls `println` as expected. 73 | 74 | The `side-effects` macro is deliberately simple: it focuses on the arguments 75 | passed to the mock -- and that is what it returns -- but only allows for a 76 | single return value (either `nil` or the specified value), even if there are 77 | multiple calls to the mocked function. The return value is fixed for all calls, 78 | and does not depend on the arguments passed to the mock. If you need more 79 | complex behavior in a mock, you can use `with-redefs` or similar, and write 80 | your own argument capturing logic, if needed. 81 | 82 | For comparison, the following `side-effects` call and the `with-redefs` code 83 | have the same effect: 84 | 85 | ```clojure 86 | (side-effects [[my-pred true] println] (my-code 42)) 87 | ;; is equivalent to: 88 | (let [calls (atom [])] 89 | (with-redefs [my-pred (fn [& args] (swap! calls conj args) true) 90 | println (fn [& args] (swap! calls conj args) nil)] 91 | (my-code 42) 92 | @calls)) 93 | ``` 94 | 95 | Because `side-effects` can return multiple results, and each result is a sequence 96 | of values, it often helps to combine `side-effects` with `more-of`: 97 | 98 | ```clojure 99 | (defn my-pred [x] (= 42 x)) 100 | (defn my-code [x] (if (my-pred x) (println "good") (println "bad"))) 101 | 102 | (defexpect my-code-test 103 | (expect (more-of [[x] [msg] :as all] 104 | 2 (count all) ; check there were just two calls 105 | 99 x 106 | "bad" msg) 107 | (side-effects [my-pred println] (my-code 99))) 108 | (expect (more-of [[x] [msg] :as all] 109 | 2 (count all) ; check there were just two calls 110 | 42 x 111 | "good" msg) 112 | (side-effects [[my-pred true] println] (my-code 42)))) 113 | ``` 114 | 115 | The examples here have fairly simple argument lists: a single value in each 116 | call that is just a literal. In real world code, calls will often have multiple 117 | arguments and those might be data structures. `more-of` lets you destructure 118 | arbitrary expressions (because, under the hood, it works just like `let` or `fn`) 119 | so combine the vector destructuring for multiple arguments with key 120 | destructuring etc: 121 | 122 | ```clojure 123 | (defn my-compute [x y z] (assoc x y (inc z))) 124 | (defn processor [a b c] (my-compute {:a a :b b} :c c)) 125 | 126 | (defexpect my-compute-test 127 | (expect (more-of [[{:keys [a b]} k v] ; first call 128 | [{a2 :a b2 :b} k2 v2] ; second call, renaming keys 129 | :as all] 130 | 2 (count all) ; check there were just two calls 131 | 1 a 132 | 2 b 133 | :c k 134 | 3 v 135 | 4 a2 136 | 5 b2 137 | :c k2 138 | 6 v2) 139 | (side-effects [my-compute] 140 | (processor 1 2 3) 141 | (processor 4 5 6)))) 142 | ``` 143 | 144 | # Further Reading 145 | 146 | * [Getting Started](/doc/getting-started.md) 147 | * [Useful Predicates](/doc/useful-predicates.md) 148 | * [Collections](/doc/collections.md) 149 | * [Expecting More](/doc/more.md) 150 | * [Fixtures & Focused Test Execution](/doc/fixtures-focus.md) 151 | -------------------------------------------------------------------------------- /doc/useful-predicates.md: -------------------------------------------------------------------------------- 1 | # Useful Predicates 2 | 3 | Expectations leans heavily on predicates: you can `expect` any predicate to be true on the (actual) test value. You can use any predicate -- built into Clojure, user-defined, even Specs -- but Expectations also provides a few of its own that can be helpful when checking values are close to some expected result or within an expected range, etc. 4 | 5 | ## `approximately` 6 | 7 | ```clojure 8 | (expect (approximately 19.99) (default-package-price store)) 9 | ``` 10 | 11 | This checks that the resulting price is "close to" `19.99`. By default, `approximately` uses a "delta" of `0.001` so the above accepts values between `19.989` and `19.991` inclusively. You can provide your own delta value: 12 | 13 | ```clojure 14 | (expect (approximately 100.0 0.5) (total-coverage tests)) 15 | ``` 16 | 17 | This checks that the coverage value is within +/- `0.5` of `100.0` (i.e., `99.5 <= v <= 100.5`). 18 | 19 | ## `between` and `between'` 20 | 21 | Instead of expecting a result to be close to a value, you may expect a result to be within a known range of values. These two predicates offer an inclusive range and an exclusive range respectively. 22 | 23 | ```clojure 24 | ;; (rand-int 10) will produce 0, 1, 2, .., 9 25 | (expect (between 0 9) (rand-int 10)) 26 | (expect (between' -1 10) (rand-int 10)) 27 | ;; if you want an inclusive/exclusive range: 28 | (let [n 100] 29 | (expect (between 0 (dec n)) (rand-int n))) 30 | ``` 31 | 32 | ## `functionally` 33 | 34 | Sometimes you have two functions that you expect to be "functionally equivalent". In other words, when they are given the same argument, they should produce the same value perhaps via different computations. This can also emerge when you have a data structure and a function under test where the result of that function can also be computed via a simple test function for a given set of input data. 35 | 36 | Here's a simple example showing that two ways to compute the square of a number are "functionally equivalent" (for the given test values): 37 | 38 | ```clojure 39 | (expect (functionally (fn [n] (reduce + (repeat n n))) 40 | (fn [n] (* n n))) 41 | (from-each [i (range 100)] 42 | i)) 43 | ``` 44 | 45 | # Further Reading 46 | 47 | * [Getting Started](/doc/getting-started.md) 48 | * [Collections](/doc/collections.md) 49 | * [Expecting More](/doc/more.md) 50 | * [Expecting Side Effects](/doc/side-effects.md) 51 | * [Fixtures & Focused Test Execution](/doc/fixtures-focus.md) 52 | -------------------------------------------------------------------------------- /dooopts.edn: -------------------------------------------------------------------------------- 1 | {:debug false :paths {:planck "planck --compile-opts planckopts.edn"}} 2 | -------------------------------------------------------------------------------- /planckopts.edn: -------------------------------------------------------------------------------- 1 | {:warnings {:private-var-access false}} 2 | -------------------------------------------------------------------------------- /resources/clj-kondo.exports/com.github.seancorfield/expectations/config.edn: -------------------------------------------------------------------------------- 1 | {:hooks 2 | {:analyze-call 3 | {expectations.clojure.test/more-> 4 | hooks.com.github.seancorfield.expectations/more-> 5 | expectations.clojure.test/more-of 6 | hooks.com.github.seancorfield.expectations/more-of}} 7 | :lint-as 8 | {expectations.clojure.test/defexpect clojure.test/deftest 9 | expectations.clojure.test/from-each clojure.core/for 10 | expectations.clojure.test/=? clojure.core/=}} 11 | -------------------------------------------------------------------------------- /resources/clj-kondo.exports/com.github.seancorfield/expectations/hooks/com/github/seancorfield/expectations.clj_kondo: -------------------------------------------------------------------------------- 1 | (ns hooks.com.github.seancorfield.expectations 2 | (:require [clj-kondo.hooks-api :as api])) 3 | 4 | (defn more-> [{:keys [node]}] 5 | (let [tail (rest (:children node)) 6 | rewritten 7 | (api/list-node 8 | (list* 9 | (api/token-node 'cond->) 10 | (api/token-node 'nil) 11 | tail))] 12 | {:node rewritten})) 13 | 14 | (defn more-of [{:keys [node]}] 15 | (let [bindings (fnext (:children node)) 16 | pairs (partition 2 (nnext (:children node))) 17 | rewritten 18 | (api/list-node 19 | (list* 20 | (api/token-node 'fn) 21 | (api/vector-node (vector bindings)) 22 | (map (fn [[e a]] 23 | (api/list-node 24 | (list 25 | (api/token-node 'expectations.clojure.test/expect) 26 | e 27 | a))) 28 | pairs)))] 29 | {:node rewritten})) 30 | -------------------------------------------------------------------------------- /src/expectations/clojure/test.cljc: -------------------------------------------------------------------------------- 1 | ;; copyright (c) 2018-2024 sean corfield, all rights reserved 2 | 3 | (ns expectations.clojure.test 4 | "This namespace provides compatibility with `clojure.test` and related tooling. 5 | 6 | This namespace should be used standalone, without requiring the 'expectations' 7 | namespace -- this provides a translation layer from Expectations syntax down 8 | to `clojure.test` functionality." 9 | (:require [clojure.data :as data] 10 | [clojure.string :as str] 11 | #?(:cljs [planck.core]) 12 | #?(:clj [clojure.test :as t] 13 | :cljs [cljs.test :include-macros true :as t]) 14 | #?(:clj [clojure.spec.alpha :as s]) 15 | #?(:cljs [cljs.spec.alpha :as s]) 16 | #?@(:cljs [pjstadig.humane-test-output 17 | pjstadig.print 18 | pjstadig.util]))) 19 | 20 | (def humane-test-output? 21 | "If Humane Test Output is available, activate it, and enable compatibility 22 | of our `=?` with it. 23 | 24 | This Var will be `true` if Humane Test Output is available and activated, 25 | otherwise it will be `nil`." 26 | #?(:clj (try (require 'pjstadig.humane-test-output) 27 | ((resolve 'pjstadig.humane-test-output/activate!)) 28 | true 29 | (catch Exception _)) 30 | :cljs (do 31 | (defmethod cljs.test/report [:cljs.test/default :fail] 32 | [event] 33 | (#'pjstadig.util/report- 34 | (if (:diffs event) 35 | event 36 | (pjstadig.print/convert-event event)))) 37 | ; This should be true for normal operation, false for testing 38 | ; this framework and running the :negative tests. 39 | true))) 40 | 41 | (defn ^:no-doc illegal-argument [s] 42 | (#?(:clj IllegalArgumentException. :cljs js/Error.) s)) 43 | 44 | ;; stub functions for :refer compatibility: 45 | (defn- bad-usage [s] 46 | `(throw (illegal-argument (str ~s " should only be used inside expect")))) 47 | 48 | (defmacro =? 49 | "Internal fuzzy-equality test (clojure.test/assert-expr)." 50 | [expected actual & [form]] 51 | (bad-usage "=?")) 52 | 53 | (defmacro in 54 | "`(expect expected (in actual))` -- expect a subset of a collection. 55 | 56 | If `actual` is a hash map, `expected` can be a hash map of key/value pairs 57 | that you expect to be in the `actual` result (there may be other key/value 58 | pairs, which are ignored). 59 | 60 | If `actual` is a set, vector, or list, `expected` can be any value that 61 | you expect to be a member of the `actual` data. 62 | 63 | `(expect {:b 2} (in {:a 1 :b 2 :c 3}))` 64 | `(expect 2 (in #{1 2 3}))` 65 | `(expect 2 (in [1 2 3]))` 66 | 67 | `in` may only be used inside `expect` and is a purely syntactic construct. 68 | This macro can be `refer`'d to satisfy tooling like `clj-kondo`." 69 | [coll] 70 | (bad-usage "in")) 71 | 72 | (defmacro from-each 73 | "`(expect expected (from-each [v coll] (f v)))` -- expect this to be true 74 | for each element of collection. `(f v)` is the actual result. 75 | 76 | Equivalent to: `(doseq [v coll] (expect expected (f v)))` 77 | 78 | `(expect even? (from-each [v (range 10)] (* 2 v)))` 79 | 80 | `from-each` may only be used inside `expect` and is a purely syntactic construct. 81 | This macro can be `refer`'d to satisfy tooling like `clj-kondo`." 82 | [bindings & body] 83 | (bad-usage "from-each")) 84 | 85 | (defmacro more-of 86 | "`(expect (more-of destructuring expected1 actual1 ...) actual)` -- provide 87 | multiple expectations on `actual` based on binding it against the 88 | `destructuring` expression (like in a `let`) and then expecting things about 89 | its subcomponents. 90 | 91 | Equivalent to: `(let [destructuring actual] (expect expected1 actual1) ...)` 92 | 93 | `(expect (more-of [a b] string? a int? b) [\"test\" 42])` 94 | 95 | `more-of` may only be used inside `expect` and is a purely syntactic construct. 96 | This macro can be `refer`'d to satisfy tooling like `clj-kondo`." 97 | [destructuring & expected-actual-pairs] 98 | (bad-usage "more-of")) 99 | 100 | (defmacro more-> 101 | "`(expect (more-> expected1 (threaded1) ...) actual)` -- provide multiple 102 | expectations on `actual` based on threading it into various expressions. 103 | 104 | Equivalent to: `(do (expect expected1 (-> actual (threaded1))) ...)` 105 | 106 | `(expect (more-> string? (first) int? (second)) [\"test\" 42])` 107 | 108 | `more->` may only be used inside `expect` and is a purely syntactic construct. 109 | This macro can be `refer`'d to satisfy tooling like `clj-kondo`." 110 | [& expected-threaded-pairs] 111 | (bad-usage "more->")) 112 | 113 | (defmacro more 114 | "`(expect (more expected1 ...) actual)` -- provide multiple expectations 115 | on `actual` as a series of expected results. 116 | 117 | Equivalent to: `(do (expect expected1 actual) ...)` 118 | 119 | `(expect (more int? even?) 42)` 120 | 121 | `more` may only be used inside `expect` and is a purely syntactic construct. 122 | This macro can be `refer`'d to satisfy tooling like `clj-kondo`." 123 | [& expecteds] 124 | (bad-usage "more")) 125 | 126 | (defn ^:no-doc spec? 127 | "Detects whether an expected expression seems to be a Spec." 128 | [e] 129 | (and (keyword? e) (s/get-spec e))) 130 | 131 | (defn ^:no-doc str-match 132 | "Returns the match off of the beginning of two strings." 133 | [a b] 134 | (loop [a-seq (seq a) 135 | b-seq (seq b) 136 | match 0] 137 | (if (= (first a-seq) (first b-seq)) 138 | (recur (next a-seq) (next b-seq) (inc match)) 139 | (subs a 0 match)))) 140 | 141 | (defn ^:no-doc str-diff 142 | "Returns three strings [only-in-a only-in-b in-both]" 143 | [a b] 144 | (let [match (str-match a b) 145 | match-len (count match)] 146 | [(subs a match-len) (subs b match-len) match])) 147 | 148 | (defn ^:no-doc str-msg 149 | "Given output from str-diff, produce a message about the difference." 150 | [a b in-both] 151 | (str "matches: " (pr-str in-both) 152 | "\n>>> expected diverges: " (pr-str 153 | (clojure.string/replace a in-both "")) 154 | "\n>>> actual diverges: " (pr-str 155 | (clojure.string/replace b in-both "")) 156 | "\n")) 157 | 158 | (defn ^:no-doc =?-impl 159 | "Perform comparison of expected vs actual, build :expected and :actual forms." 160 | [{:keys [e e# a a# conform?]}] 161 | (cond conform? 162 | [(s/valid? e# a#) 163 | (s/explain-str e# a#) 164 | (list 's/valid? e a) 165 | (list 'not (list 's/valid? e a#))] 166 | (fn? e#) 167 | [(e# a#) 168 | (str a " did not satisfy " e "\n") 169 | (list e a) 170 | (list 'not (list e a#))] 171 | (isa? (type e#) 172 | #?(:clj java.util.regex.Pattern 173 | :cljs (type #"regex"))) 174 | [(some? (re-find e# a#)) 175 | (str (pr-str a#) " did not match " (pr-str e#) "\n") 176 | (list 're-find e a) 177 | (list 'not (list 're-find e# a#))] 178 | #?(:clj (and (class? e#) (class? a#)) 179 | :cljs false) ; maybe figure this out later 180 | [(isa? a# e#) ; (expect parent child) 181 | (str a# " is not derived from " e# "\n") 182 | (list 'isa? a e) 183 | (list 'not (list 'isa? a# e#))] 184 | #?(:clj (class? e#) 185 | :cljs false) ; maybe figure this out later 186 | [(instance? e# a#) ; (expect klazz object) 187 | (str a# 188 | #?(:clj (str " (" (class a#) ")")) 189 | " is not an instance of " e# "\n") 190 | (list 'instance? e a) 191 | #?(:clj (class a#))] 192 | :else 193 | [(= e# a#) 194 | (when (and (string? e#) (string? a#) (not= e# a#)) 195 | (let [[_# _# in-both#] (str-diff e# a#)] 196 | (str-msg e# a# in-both#))) 197 | (list '= e a) 198 | (list 'not= e# a#)])) 199 | 200 | ;; smart equality extension to clojure.test assertion -- if the expected form 201 | ;; is a predicate (function) then the assertion is equivalent to (is (e a)) 202 | ;; rather than (is (= e a)) and we need the type check done at runtime, not 203 | ;; as part of the macro translation layer 204 | (defmethod #?(:clj t/assert-expr 205 | :cljs cljs.test$macros/assert-expr) 206 | '=? 207 | #?(:clj [msg form] :cljs [menv msg form]) 208 | ;; (is (=? val-or-pred expr)) 209 | (let [[_ e a form'] form 210 | conform? (boolean (spec? e))] 211 | `(let [e# ~e 212 | a# ~a 213 | f# ~form' 214 | [r# m# ef# af#] (=?-impl {:e '~e :e# e# 215 | :a '~a :a# a# 216 | :conform? ~conform?}) 217 | humane?# (and humane-test-output? (not (fn? e#)) (not ~conform?))] 218 | (if r# 219 | (t/do-report {:type :pass, :message ~msg, 220 | :expected '~form, :actual (if (fn? e#) 221 | (list '~e a#) 222 | a#)}) 223 | (t/do-report 224 | {:type :fail, 225 | :message (if m# (if-let [msg# ~msg] (str msg# "\n" m#) m#) ~msg) 226 | :diffs (if humane?# 227 | [[a# (take 2 (data/diff e# a#))]] 228 | []) 229 | :expected (cond humane?# 230 | e# 231 | f# 232 | f# 233 | ef# 234 | ef# 235 | :else 236 | '~form) 237 | :actual (cond af# 238 | af# 239 | humane?# 240 | [a#] 241 | :else 242 | (list '~'not (list '~'=? e# a#)))})) 243 | r#))) 244 | 245 | (comment 246 | (data/diff "foo" ["bar"]) 247 | ) 248 | 249 | (defmacro ^:no-doc ? 250 | "Wrapper for forms that might throw an exception so exception class names 251 | can be used as predicates. This is only needed for `more->` so that you can 252 | thread exceptions into code that can parse information out of them, to be 253 | used with various expect predicates." 254 | [form] 255 | `(try ~form 256 | (catch #?(:clj Throwable 257 | :cljs :default) 258 | t# 259 | t#))) 260 | 261 | (defn ^:no-doc all-report 262 | "Given an atom in which to accumulate results, return a function that 263 | can be used in place of `clojure.test/do-report`, which simply remembers 264 | all the reported results. 265 | 266 | This is used to support the semantics of `expect/in`." 267 | [store] 268 | (fn [m] 269 | (swap! store update (:type m) (fnil conj []) m))) 270 | 271 | (defmacro expect 272 | "Translate Expectations DSL to `clojure.test` language. 273 | 274 | These are approximate translations for the most basic forms: 275 | 276 | `(expect actual)` => `(is actual)` 277 | 278 | `(expect expected actual)` => `(is (= expected actual))` 279 | 280 | `(expect predicate actual)` => `(is (predicate actual))` 281 | 282 | `(expect regex actual)` => `(is (re-find regex actual))` 283 | 284 | `(expect ClassName actual)` => `(is (instance? ClassName actual))` 285 | 286 | `(expect ExceptionType actual)` => `(is (thrown? ExceptionType actual))` 287 | 288 | `(expect spec actual)` => `(is (s/valid? spec actual))` 289 | 290 | An optional third argument can be provided: a message to be included 291 | in the output if the test fails. 292 | 293 | In addition, `actual` can be `(from-each [x coll] (computation-of x))` 294 | or `(in set-of-results)` or `(in larger-hash-map)`. 295 | 296 | Also, `expect` can be one of `(more predicate1 .. predicateN)`, 297 | `(more-> exp1 expr1 .. expN exprN)` where `actual` is threaded through 298 | each expression `exprX` and checked with the expected value `expX`, 299 | or `(more-of binding exp1 val1 .. expN valN)` where `actual` is 300 | destructured using the `binding` and then each expected value `expX` 301 | is used to check each `valX` -- expressions based on symbols in the 302 | `binding`." 303 | ([a] `(t/is ~a nil)) 304 | ([e a] `(expect ~e ~a nil true ~e)) 305 | ([e a msg] `(expect ~e ~a ~msg true ~e)) 306 | ([e a msg ex? e'] 307 | (let [within (if (and (sequential? e') 308 | (symbol? (first e')) 309 | (= "expect" (name (first e')))) 310 | (pr-str e') 311 | (pr-str (list 'expect e' a))) 312 | msg' `(str/join 313 | "\n" 314 | (cond-> [] 315 | ~msg 316 | (conj ~msg) 317 | ~(not= e e') 318 | (conj (str " within: " ~within)) 319 | :else 320 | (conj ~(str (pr-str a) "\n"))))] 321 | (cond 322 | (and (sequential? a) 323 | (symbol? (first a)) 324 | (= "from-each" (name (first a)))) 325 | (let [[_ bindings & body] a] 326 | (if (= 1 (count body)) 327 | `(doseq ~bindings 328 | (expect ~e ~(first body) ~msg ~ex? ~e)) 329 | `(doseq ~bindings 330 | (expect ~e (do ~@body) ~msg ~ex? ~e)))) 331 | 332 | (and (sequential? a) 333 | (symbol? (first a)) 334 | (= "in" (name (first a)))) 335 | (let [form `(~'expect ~e ~a)] 336 | `(let [e# ~e 337 | a# ~(second a) 338 | not-in# (str '~e " not found in " a#) 339 | msg# (if (seq ~msg') (str ~msg' "\n" not-in#) not-in#)] 340 | (cond (and (set? a#) (set? e#)) 341 | ;; special case of set in set -- report any elements from 342 | ;; expected set that are not in the actual set: 343 | (t/is (~'=? (clojure.set/difference e# a#) #{} '~form) msg#) 344 | (or (sequential? a#) (set? a#)) 345 | (let [all-reports# (atom nil) 346 | one-report# (atom nil)] 347 | ;; we accumulate any and all failures and errors but we 348 | ;; only accumulate passes if each sequential expectation 349 | ;; fully passes (i.e., no failures or errors) 350 | (with-redefs [t/do-report (all-report one-report#)] 351 | (doseq [a'# a#] 352 | (expect e# a'# msg# ~ex? ~form) 353 | (if (or (contains? @one-report# :error) 354 | (contains? @one-report# :fail)) 355 | (do 356 | (when (contains? @one-report# :fail) 357 | (swap! all-reports# 358 | update :fail into (:fail @one-report#))) 359 | (when (contains? @one-report# :error) 360 | (swap! all-reports# 361 | update :error into (:error @one-report#)))) 362 | (when (contains? @one-report# :pass) 363 | (swap! all-reports# 364 | update :pass into (:pass @one-report#)))) 365 | (reset! one-report# nil))) 366 | 367 | (if (contains? @all-reports# :pass) 368 | ;; report all the passes (and no failures or errors) 369 | (doseq [r# (:pass @all-reports#)] (t/do-report r#)) 370 | (do 371 | (when-let [r# (first (:error @all-reports#))] 372 | (t/do-report r#)) 373 | (when-let [r# (first (:fail @all-reports#))] 374 | (t/do-report r#))))) 375 | (map? a#) 376 | (if (map? e#) 377 | (let [submap# (select-keys a# (keys e#))] 378 | (t/is (~'=? e# submap# '~form) ~msg')) 379 | (throw (illegal-argument "'in' requires map or sequence"))) 380 | :else 381 | (throw (illegal-argument "'in' requires map or sequence"))))) 382 | 383 | (and (sequential? e) 384 | (symbol? (first e)) 385 | (= "more" (name (first e)))) 386 | (let [sa (gensym) 387 | es (mapv (fn [e] `(expect ~e ~sa ~msg ~ex? ~e')) (rest e))] 388 | `(let [~sa (? ~a)] ~@es)) 389 | 390 | (and (sequential? e) 391 | (symbol? (first e)) 392 | (= "more->" (name (first e)))) 393 | (let [sa (gensym) 394 | es (mapv (fn [[e a->]] 395 | (if (and (sequential? a->) 396 | (symbol? (first a->)) 397 | (let [s (name (first a->))] 398 | (or (str/ends-with? s "->") 399 | (str/ends-with? s "->>")))) 400 | `(expect ~e (~(first a->) ~sa ~@(rest a->)) ~msg false ~e') 401 | `(expect ~e (-> ~sa ~a->) ~msg false ~e'))) 402 | (partition 2 (rest e)))] 403 | `(let [~sa (? ~a)] ~@es)) 404 | 405 | (and (sequential? e) 406 | (symbol? (first e)) 407 | (= "more-of" (name (first e)))) 408 | (let [es (mapv (fn [[e a]] `(expect ~e ~a ~msg ~ex? ~e')) 409 | (partition 2 (rest (rest e))))] 410 | `(let [~(second e) ~a] ~@es)) 411 | 412 | #?(:clj (and ex? (symbol? e) (resolve e) (class? (resolve e))) 413 | :cljs (and ex? 414 | (symbol? e) 415 | (planck.core/find-var e) 416 | (or (= 'js/Error e) 417 | ; is it a symbol which is not a predicate? 418 | (and (fn? (deref (planck.core/find-var e))) 419 | (not= (pr-str (deref (planck.core/find-var e))) 420 | "#object[Function]"))))) 421 | #?(:clj (if (isa? (resolve e) Throwable) 422 | `(t/is (~'thrown? ~e ~a) ~msg') 423 | `(t/is (~'=? ~e ~a) ~msg')) 424 | :cljs (if (= 'js/Error e) 425 | `(t/is (~'thrown? ~e ~a) ~msg') 426 | `(t/is (~'instance? ~e ~a) ~msg'))) 427 | 428 | :else 429 | `(t/is (~'=? ~e ~a) ~msg'))))) 430 | 431 | (comment 432 | (macroexpand '(expect (more-> 1 :a 2 :b 3 (-> :c :d)) {:a 1 :b 2 :c {:d 4}})) 433 | (macroexpand '(expect (more-of a 2 a) 4)) 434 | (macroexpand '(expect (more-of {:keys [a b c]} 1 a 2 b 3 c) {:a 1 :b 2 :c 3}))) 435 | 436 | (defn- contains-expect? 437 | "Given a form, return `true` if it contains any calls to the 'expect' macro. 438 | 439 | As of #28, we also recognize qualified 'expect' calls." 440 | [e] 441 | (when (and (coll? e) (not (vector? e))) 442 | (or (and (symbol? (first e)) 443 | (str/starts-with? (name (first e)) "expect")) 444 | (some contains-expect? e)))) 445 | 446 | (defmacro defexpect 447 | "Given a name (a symbol that may include metadata) and a test body, 448 | produce a standard `clojure.test` test var (using `deftest`)." 449 | [n & body] 450 | (if (and (= (count body) 2) 451 | (not (some contains-expect? body))) 452 | (throw (ex-info (str "defexpect " n " should contain at least one 'expect' form") 453 | {:name n, :body body})) 454 | ;; #13 match deftest behavior starting in 2.0.0 455 | `(t/deftest ~n ~@body))) 456 | 457 | (defmacro expecting 458 | "The Expectations version of `clojure.test/testing`." 459 | [string & body] 460 | `(t/testing ~string ~@body)) 461 | 462 | (defmacro side-effects 463 | "Given a vector of functions to track calls to, execute the body. 464 | 465 | Returns a vector of each set of arguments used in calls to those 466 | functions. The specified functions will not actually be called: 467 | only their arguments will be tracked. If you need the call to return 468 | a specific value, the function can be given as a pair of its name 469 | and the value you want its call(s) to return. Functions given just 470 | by name will return `nil`." 471 | [fn-vec & forms] 472 | (when-not (vector? fn-vec) 473 | (throw (illegal-argument "side-effects requires a vector as its first argument"))) 474 | (let [mocks (reduce (fn [m f-spec] 475 | (if (vector? f-spec) 476 | (assoc m (first f-spec) (second f-spec)) 477 | (assoc m f-spec nil))) 478 | {} 479 | fn-vec) 480 | called-args (gensym "called-args")] 481 | `(let [~called-args (atom [])] 482 | (with-redefs ~(reduce-kv (fn [defs f v] 483 | (conj defs 484 | f 485 | `(fn [& args#] 486 | (swap! ~called-args conj args#) 487 | ~v))) 488 | [] 489 | mocks) 490 | ~@forms) 491 | @~called-args))) 492 | 493 | (defn approximately 494 | "Given a value and an optional delta (default 0.001), return a predicate 495 | that expects its argument to be within that delta of the given value." 496 | ([^double v] (approximately v 0.001)) 497 | ([^double v ^double d] 498 | (fn [x] (<= (- v (Math/abs d)) x (+ v (Math/abs d)))))) 499 | 500 | (defn between 501 | "Given a pair of (numeric) values, return a predicate that expects its 502 | argument to be be those values or between them -- inclusively." 503 | [a b] 504 | (fn [x] (<= a x b))) 505 | 506 | (defn between' 507 | "Given a pair of (numeric) values, return a predicate that expects its 508 | argument to be (strictly) between those values -- exclusively." 509 | [a b] 510 | (fn [x] (< a x b))) 511 | 512 | (defn functionally 513 | "Given a pair of functions, return a custom predicate that checks that they 514 | return the same result when applied to a value. May optionally accept a 515 | 'difference' function that should accept the result of each function and 516 | return a string explaininhg how they actually differ. 517 | For explaining strings, you could use expectations/strings-difference. 518 | (only when I port it across!) 519 | 520 | Right now this produces pretty awful failure messages. FIXME!" 521 | ([expected-fn actual-fn] 522 | (functionally expected-fn actual-fn (constantly "not functionally equivalent"))) 523 | ([expected-fn actual-fn difference-fn] 524 | (fn [x] 525 | (let [e-val (expected-fn x) 526 | a-val (actual-fn x)] 527 | (t/is (= e-val a-val) (difference-fn e-val a-val)))))) 528 | 529 | #?(:clj (defn use-fixtures 530 | "Wrap test runs in a fixture function to perform setup and 531 | teardown. Using a fixture-type of `:each` wraps every test 532 | individually, while `:once` wraps the whole run in a single function. 533 | 534 | Like `cljs.test/use-fixtures`, also accepts hash maps with `:before` 535 | and/or `:after` keys that specify 0-arity functions to invoke 536 | before/after the test/run." 537 | [fixture-type & fs] 538 | (apply t/use-fixtures fixture-type 539 | (map (fn [f] 540 | (if (map? f) 541 | (fn [t] 542 | (when-let [before (:before f)] 543 | (before)) 544 | (t) 545 | (when-let [after (:after f)] 546 | (after))) 547 | f)) 548 | fs)))) 549 | 550 | #?(:cljs (defmacro use-fixtures 551 | "Wrap test runs in a fixture function to perform setup and 552 | teardown. Using a fixture-type of `:each` wraps every test 553 | individually, while `:once` wraps the whole run in a single function. 554 | 555 | Hands off to `cljs.test/use-fixtures`, also accepts hash maps with `:before` 556 | and/or `:after` keys that specify 0-arity functions to invoke 557 | before/after the test/run." 558 | [fixture-type & fs] 559 | `(cljs.test/use-fixtures ~fixture-type ~@fs))) 560 | 561 | (defn from-clojure-test 562 | "Intern the specified symbol from `clojure.test` as a symbol in 563 | `expectations.clojure.test` with the same value and metadata." 564 | [f] 565 | (try 566 | (let [tf (symbol #?(:clj "clojure.test" 567 | :cljs "cljs.test") 568 | (name f)) 569 | v (#?(:clj resolve 570 | :cljs planck.core/find-var) 571 | tf) 572 | m (meta v)] 573 | (#?(:clj intern 574 | :cljs planck.core/intern) 575 | 'expectations.clojure.test 576 | (with-meta f 577 | (update m 578 | :doc 579 | str 580 | (str #?(:clj "\n\nImported from clojure.test." 581 | :cljs "\n\nImported from cljs.test")))) 582 | (deref v))) 583 | (catch #?(:clj Throwable 584 | :cljs :default) _))) 585 | 586 | 587 | ;; bring over other useful clojure.test functions: 588 | (doseq [f '[#?@(:clj [run-all-tests run-tests run-test-var test-all-vars test-ns with-test]) 589 | run-test test-var test-vars]] 590 | (from-clojure-test f)) 591 | -------------------------------------------------------------------------------- /test/expectations/clojure/test_macros.cljc: -------------------------------------------------------------------------------- 1 | ;; copyright (c) 2019-2020 sean corfield, all rights reserved 2 | 3 | (ns expectations.clojure.test-macros 4 | "Macros to support testing the testing framework." 5 | (:require #?(:clj [clojure.test :refer [is do-report] :as t] 6 | :cljs [cljs.test :refer [do-report assert-expr] 7 | :refer-macros [is assert-expr] :as t]) 8 | #?(:cljs [cljs.spec.alpha :as s]) 9 | #?(:clj [expectations.clojure.test :as sut] 10 | :cljs [expectations.clojure.test :include-macros true :as sut]))) 11 | 12 | (defmacro is-not' 13 | "Construct a negative test for an expectation with a symbolic failure." 14 | [expectation failure & [msg]] 15 | `(let [results# (atom nil)] 16 | (with-redefs [do-report (sut/all-report results#)] 17 | ~expectation) 18 | (t/is (some (fn [fail#] 19 | (= '~failure (:actual fail#))) 20 | (:fail @results#))) 21 | (when ~msg 22 | (t/is (some (fn [fail#] 23 | (re-find ~msg (:message fail#))) 24 | (:fail @results#)))))) 25 | 26 | (defmacro is-not 27 | "Construct a negative test for an expectation with a value-based failure." 28 | [expectation failure & [msg]] 29 | `(let [results# (atom nil)] 30 | (with-redefs [do-report (sut/all-report results#)] 31 | ~expectation) 32 | (t/is (some (fn [fail#] 33 | (= ~failure (:actual fail#))) 34 | (:fail @results#))) 35 | (when ~msg 36 | (t/is (some (fn [fail#] 37 | (re-find ~msg (:message fail#))) 38 | (:fail @results#)))))) 39 | 40 | (defmacro passes 41 | "Construct a positive test for an expectation with a predicate-based success. 42 | 43 | This is needed for cases where a successful test wraps a failing behavior, 44 | such as `thrown?`, i.e., `(expect ExceptionType actual)`" 45 | [expectation success] 46 | `(let [results# (atom nil)] 47 | (with-redefs [do-report (sut/all-report results#)] 48 | ~expectation) 49 | (t/is (some (fn [pass#] 50 | (~success (:actual pass#))) 51 | (:pass @results#))))) 52 | -------------------------------------------------------------------------------- /test/expectations/clojure/test_spec.cljs: -------------------------------------------------------------------------------- 1 | ;; copyright (c) 2020 sean corfield, all rights reserved 2 | 3 | (ns expectations.clojure.test-spec 4 | "Need to define spec in a separate compilation unit from where it is 5 | referenced in cljs." 6 | (:require [cljs.spec.alpha :as s])) 7 | 8 | (s/def ::small-value (s/and pos-int? #(< % 100))) 9 | 10 | -------------------------------------------------------------------------------- /test/expectations/clojure/test_test.cljc: -------------------------------------------------------------------------------- 1 | ;; copyright (c) 2019-2024 sean corfield, all rights reserved 2 | 3 | (ns expectations.clojure.test-test 4 | "Test the testing framework -- this is sometimes harder than you might think! 5 | 6 | Tests marked `^:negative` will not pass with Humane Test Output enabled 7 | because it manipulates the report data which my `is-not` macros rely on." 8 | (:require [clojure.string :as str] 9 | #?(:clj [expectations.clojure.test-macros :refer 10 | [is-not' is-not passes]] 11 | :cljs [expectations.clojure.test-macros 12 | :refer-macros [is-not' is-not passes]]) 13 | 14 | #?(:clj [clojure.test :refer [deftest is do-report testing]] 15 | :cljs [cljs.test :include-macros true 16 | :refer [do-report assert-expr] 17 | :refer-macros [deftest is testing assert-expr 18 | use-fixtures]]) 19 | #?(:cljs [cljs.spec.alpha :as s]) 20 | #?(:cljs [expectations.clojure.test-spec]) 21 | #?(:clj [expectations.clojure.test :refer 22 | [from-each in more more-> more-of =?] :as sut] 23 | :cljs [expectations.clojure.test 24 | :include-macros true 25 | :refer-macros [from-each in more more-> more-of =?] 26 | :as sut]))) 27 | 28 | ; The macros are in test_macros.cljc to support ClojureScript. 29 | 30 | (deftest predicate-test 31 | (is (sut/expect even? (+ 1 1))) 32 | (is (sut/expect empty? (list))) 33 | (is-not' (sut/expect even? (+ 1 1 1)) (not (even? 3))) 34 | (is-not' (sut/expect empty? [1]) (not (empty? [1])))) 35 | 36 | (deftest equality-test 37 | (is (sut/expect 1 (* 1 1))) 38 | (is (sut/expect "foo" (str "f" "oo")))) 39 | 40 | (deftest ^:negative not-equality-test 41 | (is-not' (sut/expect 2 (* 1 1)) (not= 2 1)) 42 | (is-not' (sut/expect "fool" (str "f" "oo")) (not= "fool" "foo"))) 43 | 44 | (deftest ^:negative message-test 45 | (is-not' (sut/expect even? (+ 1 1 1) "It's uneven!") 46 | (not (even? 3)) 47 | #"uneven") 48 | (is-not' (sut/expect empty? [1] "It's partly full!") 49 | (not (empty? [1])) 50 | #"full") 51 | (is-not' (sut/expect 2 (* 1 1) "One times one isn't two?") 52 | (not= 2 1) 53 | #"isn't two") 54 | (is-not' (sut/expect "fool" (str "f" "oo") "No fooling around!") 55 | (not= "fool" "foo") 56 | #"fooling")) 57 | 58 | (deftest regex-test 59 | (is (sut/expect #"foo" "It's foobar!")) 60 | ;; TODO: fails because regexes never compare equal to themselves! 61 | #_(is-not' (sut/expect #"fool" "It's foobar!") (not (re-find #"fool" "It's foobar!")))) 62 | 63 | #?(:clj (def get-ex-message (or (resolve 'clojure.core/ex-message) 64 | (fn [^Throwable t] (.getMessage t)))) 65 | :cljs (def get-ex-message ex-message)) 66 | 67 | #?(:clj (deftest exception-test 68 | (passes (sut/expect ArithmeticException (/ 12 0)) 69 | (fn [ex] 70 | (let [t (Throwable->map ex)] 71 | (and (= "Divide by zero" (-> t :cause)) 72 | (or (= 'java.lang.ArithmeticException (-> t :via first :type)) 73 | (= java.lang.ArithmeticException (-> t :via first :type))))))) 74 | (passes (sut/expect Exception (/ 12 0)) 75 | (fn [ex] 76 | (let [t (Throwable->map ex)] 77 | (and (= "Divide by zero" (-> t :cause)) 78 | (or (= 'java.lang.ArithmeticException (-> t :via first :type)) 79 | (= java.lang.ArithmeticException (-> t :via first :type))))))) 80 | (is (sut/expect (more-> ArithmeticException type #"Divide by zero" get-ex-message) (/ 12 0))) 81 | (is (sut/expect (more-> Exception type #"Divide by zero" get-ex-message) (/ 12 0)))) 82 | 83 | :cljs (deftest cljs-exception-test 84 | (passes (sut/expect js/Error (throw (ex-info "foo" {}))) 85 | (fn [ex] 86 | (let [t (cljs.repl/Error->map ex)] 87 | (and (= "foo" 88 | (-> t :cause)) (or (= 'ExceptionInfo 89 | (->> t :via first :type)) 90 | (= ExceptionInfo 91 | (->> t :via first :type))))))))) 92 | 93 | #?(:clj (deftest class-test 94 | (is (sut/expect String (name :foo))) 95 | (is-not (sut/expect String :foo) clojure.lang.Keyword))) 96 | 97 | #?(:cljs (deftest class-test 98 | (is (sut/expect cljs.core/List '(a b c))) 99 | (is-not (sut/expect cljs.core/List :foo) cljs.core/Keyword))) 100 | 101 | #?(:clj (try 102 | (eval '(do 103 | (require '[clojure.spec.alpha :as s]) 104 | (s/def :small/value (s/and pos-int? #(< % 100))) 105 | (deftest spec-test 106 | (is (sut/expect :small/value (* 13 4))) 107 | (is-not' (sut/expect :small/value (* 13 40)) (not (s/valid? :small/value 520)))))) 108 | (catch Throwable _ 109 | (println "\nOmitting Spec tests for Clojure" (clojure-version))))) 110 | 111 | #?(:cljs (deftest spec-test 112 | (is (sut/expect :expectations.clojure.test-spec/small-value 113 | (* 13 4))) 114 | (is-not' 115 | (sut/expect :expectations.clojure.test-spec/small-value (* 13 40)) 116 | (not (s/valid? :expectations.clojure.test-spec/small-value 520))))) 117 | 118 | (deftest collection-test 119 | (is (sut/expect {:foo 1} (in {:foo 1 :cat 4}))) 120 | (is (sut/expect #{1 2} (in #{0 1 2 3}))) 121 | ;; TODO: need better tests here 122 | (is (nil? (sut/expect :foo (in #{:foo :bar})))) 123 | (is (nil? (sut/expect :foo (in [:bar :foo]))))) 124 | 125 | (deftest ^:negative not-collection-test 126 | (is-not' (sut/expect {:foo 1} (in {:foo 2 :cat 4})) (not= {:foo 1} {:foo 2})) 127 | 128 | ;; TODO: need better tests here 129 | (deftest grouping-more-more-of-from-each 130 | (sut/expecting "numeric behavior" 131 | (sut/expect (more-of {:keys [a b]} 132 | even? a 133 | odd? b) 134 | {:a (* 2 13) :b (* 3 13)}) 135 | (sut/expect pos? (* -3 -5))) 136 | (sut/expecting "string behavior" 137 | (sut/expect (more #"foo" "foobar" #(str/starts-with? % "f")) 138 | (str "f" "oobar")) 139 | (sut/expect #"foo" 140 | (from-each [s ["l" "d" "bar"]] 141 | (str "foo" s)))))) 142 | 143 | (deftest more-evals-once 144 | (let [counter (atom 1)] 145 | ;; issue 24: more should only evaluate actual expression once: 146 | (sut/expect (more even? even? even? pos?) 147 | (swap! counter inc)) 148 | (is (= 2 @counter))) 149 | (let [counter (atom 1)] 150 | ;; issue 24: more-> should only evaluate actual expression once: 151 | (sut/expect (more-> even? identity even? identity even? identity pos? identity) 152 | (swap! counter inc)) 153 | (is (= 2 @counter)))) 154 | 155 | (defn- dummy1 [x] (throw (ex-info "called dummy1" {:x x}))) 156 | (defn- dummy2 [x] (throw (ex-info "called dummy2" {:x x}))) 157 | 158 | (deftest side-effect-testing 159 | (testing "No side effects" 160 | (is (= [] (sut/side-effects [dummy1 [dummy2 42]] (+ 1 1))))) 161 | (testing "Basic side effects" 162 | (is (= [[2]] 163 | (sut/side-effects [dummy1 [dummy2 42]] (dummy1 (+ 1 1))))) 164 | (is (= [[2]] 165 | (sut/side-effects [dummy1 [dummy2 42]] (dummy2 (+ 1 1)))))) 166 | (testing "Mocked return values" 167 | (is (= [[2] [42]] 168 | (sut/side-effects [dummy1 [dummy2 42]] (dummy1 (dummy2 (+ 1 1)))))) 169 | (is (= [[2] [nil]] 170 | (sut/side-effects [dummy1 [dummy2 42]] (dummy2 (dummy1 (+ 1 1)))))))) 171 | 172 | (def d-t-counter (atom 0)) 173 | 174 | ; There is no cljs.test/with-test 175 | #?(:clj (sut/with-test 176 | (defn definition-test 177 | "Make sure expectations work with clojure.test/with-test." 178 | [a b c] 179 | (swap! d-t-counter inc) 180 | (* a b c)) 181 | (println "\nRunning inline tests") 182 | (reset! d-t-counter 0) 183 | (is (= 0 @d-t-counter)) 184 | (sut/expect 1 (definition-test 1 1 1)) 185 | (sut/expect 6 (definition-test 1 2 3)) 186 | (is (= 2 @d-t-counter)))) 187 | 188 | ;; these would be failing tests in 1.x but not in 2.x: 189 | (sut/defexpect deftest-equivalence-0) 190 | (sut/defexpect deftest-equivalence-1 nil) 191 | 192 | (def ^:private control (atom 0)) 193 | ;; this will succeed on its own 194 | (sut/defexpect control-test-1 (sut/expect zero? @control)) 195 | ;; then retest with a different control value 196 | (deftest control-test-2 197 | (try 198 | (reset! control 1) 199 | (is-not' (control-test-1) (not (zero? 1))) 200 | (finally 201 | (reset! control 0)))) 202 | 203 | ; Unit test for string compare routines 204 | 205 | (deftest string-test 206 | (is (= "abc" (sut/str-match "abcdef" "abcefg"))) 207 | (is (= ["def" "efg" "abc"] (sut/str-diff "abcdef" "abcefg"))) 208 | (is (= "matches: \"abc\"\n>>> expected diverges: \"def\"\n>>> actual diverges: \"efg\"\n" 209 | (sut/str-msg "abcdef" "abcefg" "abc")))) 210 | 211 | ; Test use of string compare routines as well as actual form in message 212 | ; on failure. Tests are similar, but cljs one can be run with 213 | ; humane-test-output while clj one cannot. 214 | 215 | #?(:clj (deftest ^:negative string-compare-failure-test 216 | (is-not' (sut/expect "abcdef" (str "abc" "efg")) 217 | (not= "abcdef" "abcefg") 218 | #"(?is)(str \"abc\" \"efg\").*matches: \"abc\"")) 219 | :cljs (deftest string-compare-failure-test 220 | (is-not' (sut/expect "abcdef" (str "abc" "efg")) 221 | (not= "abcdef" "abcefg") 222 | #"(?is)(str \"abc\" \"efg\").*matches: \"abc\""))) 223 | 224 | (deftest issue-19-regex-test 225 | (is (sut/expect (re-pattern "\\d+") "1000"))) 226 | 227 | #_ ; #42 throw exception at macro expansion time: 228 | (sut/defexpect issue-42 string? "explode") 229 | --------------------------------------------------------------------------------