{"id":17117477,"url":"https://github.com/holyjak/clj-concordion","last_synced_at":"2025-04-13T02:32:45.913Z","repository":{"id":62431435,"uuid":"181558427","full_name":"holyjak/clj-concordion","owner":"holyjak","description":"Developer-friendly, simple BDD tests using Clojure and clojure.test, based on Concordion.org","archived":false,"fork":false,"pushed_at":"2021-11-13T15:29:52.000Z","size":116,"stargazers_count":23,"open_issues_count":2,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-10T18:46:38.515Z","etag":null,"topics":["clojure","specification-by-example","testing-library"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/holyjak.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-04-15T20:11:24.000Z","updated_at":"2024-06-29T14:39:03.000Z","dependencies_parsed_at":"2022-11-01T21:00:34.775Z","dependency_job_id":null,"html_url":"https://github.com/holyjak/clj-concordion","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/holyjak%2Fclj-concordion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/holyjak%2Fclj-concordion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/holyjak%2Fclj-concordion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/holyjak%2Fclj-concordion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/holyjak","download_url":"https://codeload.github.com/holyjak/clj-concordion/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248657824,"owners_count":21140842,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","specification-by-example","testing-library"],"created_at":"2024-10-14T17:51:47.320Z","updated_at":"2025-04-13T02:32:45.651Z","avatar_url":"https://github.com/holyjak.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# clj-concordion\n\nDeveloper-friendly, simple BDD tests using Clojure and `clojure.test`, based on [Concordion](https://concordion.org/).\n\nWith Concordion, you can write high-level description of features in Markdown\nand document them with data examples. Hidden instrumentation (in the form of magic links)\nbinds the examples to functions in your Fixture classes (or rather Clojure test namespaces,\nin the case of clj-concordion) making it possible to verify\nthem against the code. When you run your test runner, Concordion also generates\nHTML files incorporating results of the passed or failed examples/tests, which you\ncan publish for the business audience of your code.\n\n![](https://concordion.org/img/how-it-works-markdown.png)\n\nConcordion is very simple and is quite limited at what you can do in the .md files. That is IMO a very good thing. All the logic should be in code, the specifications only supply inputs, outputs, and simple test predicates.\n\nI will not explain why to write Specification By Example (~ Behavior-Driven Development, BDD)\ntests and how to do that because the [Concordion site](https://concordion.org/) does a great\njob of that. I highly recommend that you check it out first. If you want to see how it works in Clojure, have a look at the [Coding section](#coding) below.\n\n## Usage\n\nDocs: [![cljdoc badge](https://cljdoc.org/badge/clj-concordion/clj-concordion)](https://cljdoc.org/d/clj-concordion/clj-concordion/CURRENT)\n\nTIP: clj-concordion exports [clj-kondo config for you to import](https://github.com/clj-kondo/clj-kondo/blob/master/doc/config.md#exporting-and-importing-configuration) so that Kondo understands its macro.\n\n### Preparation\n\nAdd a dependency on this project (copy the [latest dependency specification for Lein/Boot/deps.edn/Gradle/Maven from Clojars](https://clojars.org/clj-concordion)):\n\n[![Clojars Project](https://img.shields.io/clojars/v/clj-concordion.svg)](https://clojars.org/clj-concordion)\n\n(Depending on your build tool, you might also need to add an explicit dependency on `org.concordion/concordion`, [see our project.clj](https://github.com/holyjak/clj-concordion/blob/master/project.clj).)\n\n### Coding\n\nWe start by writing a Concordion specification, such as `\u003cclass path\u003e/math/Addition.md` containing the specification of a feature, with examples:\n\n```markdown\n# Addition\n\nAdding numbers follows the rules of math.\n\n### Examples\n\nAdding [1](- \"#n1\") and [3](- \"#n2\") yields [4](- \"?=add(#n1, #n2)\").\n```\n\nThe last line would render as \n\n\u003e Adding [1](- \"#n1\") and [3](- \"#n2\") yields [4](- \"?=add(#n1, #n2)\").\n\nor rather, with indication of the success:\n\n\u003e Adding [1](- \"#n1\") and [3](- \"#n2\") yields \u003cspan style=\"background-color: #afa\"\u003e4\u003c/span\u003e.\n\nNOTE: The [Concordion Instrumenting docs](https://concordion.org/instrumenting/java/markdown/) explain how these \"magical\" links work so only a brief summary: `1` and `3` are stored into the \"variables\" `#n1` and `#n2` and then we verify that `4` equals to the result of calling the function `add` with these arguments. \n\nSo you need to implement the function `add` in a test namespace with a clj-concordion _fixture_. The name of the ns needs to match the directory of the .md specification + `-test` (here: `math/` -\u003e `math-test`) and it needs to `(deffixture \u003cname of the md file\u003e)`, as we see below:\n\n```clojure\n(ns math-test\n  (:require\n      [clojure.test :refer :all]\n      [clj-concordion.core :as cc]))\n\n; The arguments are always *Strings*\n(defn add [n1 n2]\n  (int (+ (Integer/parseInt n1) (Integer/parseInt n2))))\n\n;; Create the fixture class and clojure.test test.\n;; Notice that the name of the ns and fixture corresponds to the path to the specification\n;; .md (excluding the \"-test\" suffix of the ns)\n;; We could define multiple fixtures here, if we have more .md files under math/.\n;; (so make sure they don't use the same fn name + arity without being happy to\n;; share the same implementation)\n(cc/deffixture Addition)\n\n;; Ensure Concordion is reset between each run (when running repeatedly via REPL)\n(use-fixtures :once cc/cljtest-reset-concordion)\n```\n\n(You can explore clj-concordion's own [.md specs](./test-resources)\nand the corresponding [fixture code](./test).)\n\nNow run the tests:\n\n```bash\n$ lein with-profile auto test\nlein test math-test\n\nfile:///var/folders/kg/r_8ytg7x521cvlmz_47t2rgc0000gn/T/concordion/math/Addition.html\nSuccesses: 1, Failures: 0\n\nRan 1 tests containing 0 assertions.\n0 failures, 0 errors.\n```\n\nYou can open the .html file to see the .md file rendered with results of the tests.\n\n#### Deviation from Concordion\n\n* See \"Valid expressions in specification files\" below\n* The option `declaresFullOGNL` is not supported because we have our own evaluator.\n\n##### Valid expressions in specification files\n\nWe use our own expression evaluator instead of Concordion's OGNL one, which has\nsome consequences, both positive and negative.\n\nThe expressions are a subset \u0026 superset of EDN and thus:\n\n* Constants are supported. Ex.: `[ ](- \"myFn(#var1, 'literal string', 123)\")`\n* Keywords are allowed: `\"doSomething(:action 'fire!')\"`\n* Commas are optional\n* Spaces between elements may be _necessary_ (since `=` is a valid part of name in Clojure but in\n  an expression we most likely want to break around it)\n* Special handling of `'` and `#`: all `'` are replaced with `\"` and all `#` are removed.\n  This can conflict with test values that contain them - report an issue if it happens.\n\nSee the [Expression specification](/test-resources/clj-concordion/expressions/Expressions.md) for details.\n\n#### Options\n\nNotice that `deffixture` takes a second, optional parameter, a map of options - [see the `:cc/opts` Clojure spec for valid keys and values](/src/clj_concordion/specs.clj) and\nConcordion [Fixture classes docs](https://concordion.org/coding/java/markdown/#fixture-classes) (-\u003e\n[FixtureDeclarations.java](https://github.com/concordion/concordion/blob/2.2.0/src/main/java/org/concordion/api/FixtureDeclarations.java),\n [ConcordionOptions.java](https://github.com/concordion/concordion/blob/2.2.0/src/main/java/org/concordion/api/option/ConcordionOptions.java),\n [ConcordionOptions spec](https://concordion.github.io/concordion/latest/spec/annotation/ConcordionOptions.html)) and below for their meaning.\n There is also an extensive [example in the `Addition deffixture`](https://github.com/holyjak/clj-concordion/blob/master/test/math/algebra_test.clj)\n\nThe options map replaces Concordion annotations on test classes (e.g. using `:concordion/impl-status :unimplemented` instead of `@Unimplemented`,\nan individual `:concordion.option/\u003coption-name\u003e` instead of `@ConcordionOptions(\u003coptionName\u003e=..)`, `@FailFast(onExceptionType={DatabaseUnavailableException.class})` -\u003e\n`:concordion/fail-fast-exceptions [Throwable]`) etc.), annotations on test methods such as [Before and After Hooks][before-after-hooks], and exposes additional configuration (see below).\n\n[before-after-hooks]: https://concordion.github.io/concordion/latest/spec/annotation/BeforeAndAfterMethodHooks.html\n\n\n##### Unsupported Concordion options\n\n* There is yet no support for [adding resources ~ `@ConcordionResources`](https://concordion.org/coding/java/markdown/#adding-resources) because I haven't figured out how to enable doing it for all / subset of fixtures instead of just a single fixture. Suggestions welcome!\n* ` @FullOGNL` because we use our own expression implementation instead of OGNL (and it provides ± the same power, if not more)\n* [Adding Extensions with `@org.concordion.api.extension.Extensions`](https://concordion.github.io/concordion/latest/spec/common/extension/ExtensionConfiguration.html) - you can do this instead by setting the system property `concordion.extensions`\n\n##### clj-concordion specific options\n\n* `:cc/no-asserts?` - if `true` do not log a warning when the specification has no asserts (i.e. `?=...`, `c:assertTrue=...` etc).\n* `:cc/no-trim?` - if `true` do not `trim` variable values (which we do because Concordion includes an extraneous whitespace in table-initialized variables)\n* `:cc/(before|after)-*` - see below\n\n##### Setup \u0026 tear-down functions\n\nThe `opts` argument to `deffixture` can also contain [setup/tear-down functions][before-after-hooks] run at different points of the lifecycle:\n\n```clojure\n(cc/deffixture Addition\n  {:cc/before-suite   #(println \"AdditionFixture: I run before each Suite\")\n   :cc/before-spec    #(println \"AdditionFixture: I run before each Spec\")\n   :cc/before-example (fn [exname] (println \"AdditionFixture: I run before example\" exname))\n   :cc/after-example  (fn [exname] (println \"AdditionFixture: I run after example\" exname))\n   :cc/after-spec     #(println \"AdditionFixture: I run after each Spec\")\n   :cc/after-suite    #(println \"AdditionFixture: I run after each Suite\")})\n```\n\n##### Troubleshooting: Fail fast upon an exception or a failure\n\nYou can instruct clj-concordion to stop at once when a test fails or throws an\nexception so that you can examine the runtime state. Use the following options:\n\n* `:concordion/fail-fast true` - stop on the first failure or exception\n* `:concordion/fail-fast :failures` - stop on the first failure\n* `:concordion/fail-fast :exceptions`  - stop on the first exception; same as `:concordion/fail-fast-exceptions #{Throwable}`\n* `:concordion/fail-fast-exceptions #{my.app.MyBizException, my.app.AnotherException}` - stop on the first exception of a matching type; can be combined with\n   `:concordion/fail-fast true` to also stop on test failures\n\n(Notice that it is only meaningful to include both options if you use `fail-fast-exceptions` to limit to subclass(es)\nand `fail-fast true` so that it also stops for test failures.)\n\n**BEWARE** The \"fail fast\" applies only to a single fixture / specification. If you want to make sure that only a single\nfixture runs then use `test-fixture` to run only it:\n\n```clojure\n(ns my.xy-test (:require [clj-concordion.core :as cc]))\n(cc/deffixture Addition {:concordion/fail-fast true})\n(cc/test-fixture Addition) ;; normally you'd run this from the REPL...\n```\n\n#### REPL development\n\nTo be able to run tests repeatedly from the REPL, you need to reset the previously cached results:\n\n```clojure\n(do\n  (cc/reset-concordion!)\n  (run-tests))\n```\n\n### Additional resources\n\n* [Concordion: Markdown Grammar](https://concordion.github.io/concordion/latest/spec/specificationType/markdown/Markdown.html)\n\n## Troubleshooting\n\n### General\n\nWhen troubleshooting, [enable debug logging](https://github.com/clojure/tools.logging/blob/master/README.md) for\n namespaces `clj-concordion.*`.\n\n### Common problems\n\n#### Warning: The specification  with the fixture \u003cspec\u003e seems to have no asserts\n\nThis warning is logged when the result from Concordion has zero all of the success, failure, and exception counts.\nIt is OK to ignore if your `.md` file has indeed no asserts and you can disable it by setting the options\n`{:cc/no-asserts? true}` on the `deffixture`. But if the spec has asserts and you expected to see some results then\nsomething went wrong. Enable debug logging as described above, check carefully the output (also the terminal if you connect to a remote REPL),\ntry to debug to find out what is Concordion doing.\n\n## Status\n\nStable. We expect small releases with bug fixes and occasional additional functionality as requested by the library users.\n\n## Changelog\n\n[See CHANGELOG.md](./CHANGELOG.md).\n\n## TODO\n\n* Re-run tests also when the .md files changes - add the resources/ to the watch path\n\n## Implementation\n\nNOTE: Concordion normally uses [OGNL](https://commons.apache.org/proper/commons-ognl/) to map function calls\nand property access in the specification to the fixture class. We replace it with\nour own evaulator so that we don't need to generate classes from Clojure.\n\nHow is a specification test invoked:\n\n\u003e There's 2 places where the test runner is called:\n\n\u003e Directly when invoking a test - eg. the JUnit4 ConcordionRunner, the JUnit3 ConcordionTestCase. These call FixtureRunner which calls ConcordionBuilder. You're likely to want to create something similar to ConcordionTestCase and FixtureRunner, then reuse ConcordionBuilder - eg, see Mark Derricutt's basic TestNG runner. This was created way back for Concordion 1.3.1 - you'll need to implement the Fixture and FixtureDeclarations interfaces from Concordion 2.0.0 onwards.\n\u003e\n\u003e Indirectly, from a Concordion Suite, when the concordion:run command is encountered in a specification, spawning a new test within a test. If you want to support the concordion:run command, this is where the Runner / DefaultConcordionRunner comes in. By default ConcordionBuilder plugs in a SystemPropertiesRunnerFactory which lets you override the Runner with a system property. If this doesn't suit, we could open up withRunnerFactory as an extension method - rather than overriding RunStrategy which is designed to cater for different strategies for invoking the Runner.\n\n## Development\n\n### Testing\n\n```\nlein with-profile test auto test\n```\n\n### Deployment\n\n```\n# ! Update CHANGELOG.md !\n# Ensure credentials unlocked\ngpg --quiet --batch --decrypt ~/.lein/credentials.clj.gpg\n# Deploy\nlein deploy clojars\n\n# Likely: tag, change version to \u003cnext\u003e-SNAPSHOT\ngit tag \u003cversion\u003e; git push; git push --tags\n```\n\n## License\n\n[Unlicense](https://choosealicense.com/licenses/unlicense/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fholyjak%2Fclj-concordion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fholyjak%2Fclj-concordion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fholyjak%2Fclj-concordion/lists"}