{"id":33231754,"url":"https://github.com/tdrhq/fiveam-matchers","last_synced_at":"2026-01-12T08:19:59.039Z","repository":{"id":66818937,"uuid":"545142865","full_name":"tdrhq/fiveam-matchers","owner":"tdrhq","description":"An extensible, composable matchers library for fiveam","archived":false,"fork":false,"pushed_at":"2025-04-14T17:58:49.000Z","size":89,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-14T18:49:50.371Z","etag":null,"topics":["common-lisp","testing"],"latest_commit_sha":null,"homepage":"","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tdrhq.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-03T21:24:46.000Z","updated_at":"2025-04-14T17:58:51.000Z","dependencies_parsed_at":"2024-01-11T19:33:19.819Z","dependency_job_id":"23c64631-ec49-4e58-ba30-1c50ee2d9be6","html_url":"https://github.com/tdrhq/fiveam-matchers","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tdrhq/fiveam-matchers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdrhq%2Ffiveam-matchers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdrhq%2Ffiveam-matchers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdrhq%2Ffiveam-matchers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdrhq%2Ffiveam-matchers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tdrhq","download_url":"https://codeload.github.com/tdrhq/fiveam-matchers/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdrhq%2Ffiveam-matchers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":284759739,"owners_count":27058842,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-16T02:00:05.974Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["common-lisp","testing"],"created_at":"2025-11-16T18:00:20.905Z","updated_at":"2025-11-16T19:01:25.079Z","avatar_url":"https://github.com/tdrhq.png","language":"Common Lisp","funding_links":[],"categories":["Python ##","Online editors ##"],"sub_categories":["Third-party APIs"],"readme":"\n# fiveam-matchers\n\n[![tdrhq](https://circleci.com/gh/tdrhq/fiveam-matchers.svg?style=shield)](https://app.circleci.com/pipelines/github/tdrhq/fiveam-matchers?branch=master)\n\n\nfiveam-matchers is an extensible, composable matchers library for fiveam.\n\n## Examples\n\nIt's best to explain this with examples:\n\n```lisp\n(test stuff\n  (let ((res (+ 1 2)))\n   (assert-that res (equal-to 3))))\n```\n\nIf that fails, you'll get telling you what you what the expected and\nactual results are. So far so good.\n\nWhat about this:\n\n```lisp\n\n(test stuff\n  (let ((res (list (+ 1 2) (+ 2 2))))\n    (assert-that res\n            (has-item 3)))))\n```\n\nThat makes sense too, it's basically just saying 3 is in the result\nlist. This is can be done with regular fiveam macros though. Let's try\nsomething harder.\n\n```lisp\n(test stuff\n   (let ((res (list \"car\" \"foobar\")))\n     (assert-that res\n        (has-item (starts-with \"foo\")))))\n```\n\nThis is testing that there's one item that starts with `\"foo\"`. This\ndemonstrates the composibility of matchers. `starts-with` is a\nmatcher, and `has-item` is a matcher. If the test fails, you'll get a\nmessage that looks something like this: `none of the elements matched:\na string that starts with \"foo\"`.\n\n## Custom matchers\n\nOur current set of matchers is pretty basic, we usually build matchers\nas and when we need it. If you need a new matcher, it's pretty easy to\ndefine it (and yes it composes with existing matchers)\n\nThe matcher API is inspired by Java's Hamcrest. We think it works\nnicely, and it gives us a nice template of nomenclature to copy from.\n\nLet's look at how the `starts-with` is implemented.\n\nFirst we define a matcher class. The name here isn't important since\nit won't be exposed to the end-user of your matcher:\n\n```\n(defclass starts-with-matcher (matcher)\n  ((prefix :initarg :prefix\n           :reader prefix)))\n```\n\nDefine a method that creates the matcher:\n\n```\n(defmethod starts-with ((prefix string))\n  (make-instance 'starts-with-matcher\n                  :prefix prefix))\n```\n\n(A common pattern is defining the method with an argument that's\nalready a matcher, for instance the `has-item` needs this. The\nfunction `ensure-matcher` is useful to implement this.)\n\nNow we need to do the actual checks:\n\n```\n(defmethod matchesp ((matcher starts-with-matcher)\n                     actual)\n  (and\n   (stringp actual)\n   (str:starts-with-p (prefix matcher)\n                      actual)))\n```\n\nThe next two methods are not essential. There are defaults\nimplemented, but if you're building a matcher that will be used by\nother people, we encourage implementing them. It'll give the developer\na better error message when the test fails.\n\nLet's create a method to describe the matcher:\n\n```\n(defmethod describe-self ((matcher starts-with-matcher))\n  `(\"a string that starts with `\" ,(prefix matcher) \"`\"))\n```\n\nFor convenience, you don't need to explicitly format the message. You\ncan return a list of objects that are all appended to each other. When\nthe test fails this is used to render what was actually expected (as\nopposed to the what failed).\n\nFinally, we need to describe why the test failed:\n\n```\n(defmethod describe-mismatch ((matcher starts-with-matcher) actual)\n  `(\"expected `\" ,actual \"` to start with \" ,(prefix matcher) ))\n```\n\nWhen you build a new matcher, try to allow the arguments to be\nmatchers themselves. For instance, if you create a matcher that says\nan HTML tag has an attribute value, the attribute value does not have\nto be a string: You can imagine a developer might want to check that\n`class` attribute matches having a substring.\n\n## API\n\nIn the following APIs, most functions that accept a matcher also accept a value, in which case it's treated as `(equal-to value)`.\n\n* `(equal-to val)`: check if result is `equal` to `val`.\n* `(is-not {matcher|value})`:  check that result does not match the given matcher\n* `(has-all {matchers|value}*)`: check that result matches all of the matchers\n* `(has-any {matchers|value}*)`: check if the result matches at least one of the matchers\n* `(has-typep 'type)`: check if the result is of type\n* `(is-string val)`: check if result is string\n* `(contains {matchers|value}*)`: Check if the result is a list for which\n  each element matches the corresponding matchers. (Note: the name can\n  be confusing. It \"reads\" like the result should contain that\n  element, but that's not what it is. We've kept the name to be\n  consistent with Hamcrest).\n* `(contains-in-any-order {matchers|value}*)`: Check if the result is a list\nfor which each element matches a matcher, but the order does not matter. Please be aware\nthat the current implementation is O(n!), but could be optimized to polynomial time\nin the future.\n* `(has-item {matcher|value})`: Check if the result is a list that has an item\n  that matches the matcher.\n* `(does-not-have-item {matcher|value})`: Check if the result is a list that does not have the specific item. Equivalent to `(every-item (not matcher)`, but with better descriptions.\n* `(has-length num)`: Check if the result has `length` equal to the num.\n* `(every-item {macher|value})`: Check if every item in the list matches the given matcher.\n* `(starts-with prefix)`: Check if the result starts with the given string.\n* `(contains-string needle)`: Check if the result contains the given substring\n* `(matches-regex regex)`: Check if the string matches a regex\n* `(described-as \"new description\" matcher)`: The same matcher, but with a different description.\n* `(is-not-null)`: that the value is not null\n* `(satisfying expr)`: evaluate the expr with `*` bound to the value. You can use any variables in the lexical scope. This is a nice catch all matcher. For instance, an evenp matcher looks like `(satisfying (evenp *))`.\n* `(is-string)`: Check if the object is a string\n* `(is-not-empty)`: Check if the string is not an empty string (empty string includes NIL).\n* `(signals-error-matching (error-class?) expr matcher*)`: Check if the expression signals an error that matches the given matchers. error-class defaults to `simple-error`.\n* `(error-that-matchers {matcher|value})`: Check if the given error, when converted to a string using `princ-to-string` matches the given matcher.\n* `(is-number-close-to ... \u0026key allowed-error)`: Check if the value is close to the expected value, useful for comparing doubles and floats.\n\nFinally, you can use the matchers using the `assert-that`:\n`(assert-that test-expression {matcher}*)`. Note that you can provide\nmultiple matchers to that expression, it's equivalent.\n\n## Installation with Quicklisp\n\n\n```\n(ql:quickload :fiveam-matchers)\n```\n\n## Contributing\n\nThis isn't a complete set of matchers. Aspirationally, we want to have\na version of all the matchers in Hamcrest, but we'll probably just\nbuild them as we need them. Please send us Pull Requests\nfor your matchers!\n\n## Author\n\nArnold Noronha \u003carnold@screenshotbot.io\u003e\n\n## License\n\nApache License, Version 2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftdrhq%2Ffiveam-matchers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftdrhq%2Ffiveam-matchers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftdrhq%2Ffiveam-matchers/lists"}