{"id":16026947,"url":"https://github.com/tek/xpct","last_synced_at":"2025-06-12T01:09:39.300Z","repository":{"id":57736363,"uuid":"103072721","full_name":"tek/xpct","owner":"tek","description":"monadic expectations","archived":false,"fork":false,"pushed_at":"2019-10-15T21:27:57.000Z","size":61,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-02T12:03:51.651Z","etag":null,"topics":["cats","expectations","monadic-sequencing","scala","specs2","testing"],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tek.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}},"created_at":"2017-09-11T00:43:19.000Z","updated_at":"2021-05-06T20:24:17.000Z","dependencies_parsed_at":"2022-08-24T11:20:48.249Z","dependency_job_id":null,"html_url":"https://github.com/tek/xpct","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/tek/xpct","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tek%2Fxpct","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tek%2Fxpct/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tek%2Fxpct/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tek%2Fxpct/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tek","download_url":"https://codeload.github.com/tek/xpct/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tek%2Fxpct/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259374945,"owners_count":22847872,"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":["cats","expectations","monadic-sequencing","scala","specs2","testing"],"created_at":"2024-10-08T20:04:13.674Z","updated_at":"2025-06-12T01:09:39.245Z","avatar_url":"https://github.com/tek.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# About\n**xpct** provides an algebra that abstracts assertion of conditions and extraction of values from heterogeneous data\ntypes contained in a computation effect.\n\nIn more concrete terms, you can sequence `IO`s in a for-comprehension, adding an expectation to each step, while\nextracting values contained in `Option`s or `Either`s through typeclass based matchers:\n\n```scala\nimport cats.implicits._\n\nfor {\n  a \u003c- IO.pure(Either.right(\"test\")) must contain(\"test\")\n  b \u003c- IO.pure(Option(5)) must beSome(be_\u003e=(2))\n  _ \u003c- IO.pure(s\"$a $b\") must_== \"test 5\"\n} yield ()\n\n```\n\n# Module IDs\n```sbt\n\"io.tryp\" %% \"xpct-core\" % \"0.2.1\"\n\"io.tryp\" %% \"xpct-klk\" % \"0.2.1\"\n\"io.tryp\" %% \"xpct-specs2\" % \"0.2.1\"\n\"io.tryp\" %% \"xpct-scalatest\" % \"0.2.1\"\n\"io.tryp\" %% \"xpct-utest\" % \"0.2.1\"\n```\n\n# Features\n* [typeclass based matchers](#matching-and-extracting)\n* [monadic extraction of tested values](#matching-and-extracting)\n* [arbitrary matcher nesting](#matching-and-extracting)\n* [parameterized IO for the main effect](#io-and-retrying)\n* [transparent sleep/retry mechanism](#io-and-retrying)\n* [integration with spec frameworks](#test-frameworks)\n* [cats-effect] based\n\n# Matching and Extracting\n\nMatches are based on the typeclass `Match`, where `Predicate[_]` is an arbitrary data type that represents a specific\ncondition, like `IsSome[Int](5)` (here `Target` is `Int`):\n\n```scala\ntrait Match[Predicate[_], Target, Subject, Output]\n{\n  def apply(a: Subject, fb: Predicate[Target]): AssertResult[Output]\n}\n```\n\n`Subject` is the expectable value; matches can be performed on differing types, producing a third type `Output` that is\nextracted monadically.\nWhen a `Predicate[Target]` value is passed to the implicit `must` method on the expectable, an `Xp` value is\nproduced, which uses the `Output` value returned from `Match.apply` for monadic composition, allowing you to use the\nexpectation in a for-comprehension regardless of the type of `Subject`.\n\nNesting matchers is a mechanism that is implemented in an ad-hoc way in common spec frameworks. With **xpct**,\na separate instance of `Match` can be defined that has another matcher type as its `Target`, allowing arbitrary nesting.\n\n# Included Matchers and Modifiers\n\nAll matchers and modifiers can be chained, i.e. applied to both `IO` and `Xp[IO, *]`.\n\n## Match Types\n\nAlternative syntaxes are available for matching:\n\n### Implicit Methods\n\n```scala\nIO(1).must(beSome(1)).retryEvery(100.milli)(30)\nIO(1).assert(beSome(1)).attempt.retry(5)\n```\n\n### Combinators\n\n```scala\nretryEvery(100.milli)(30)(assert(beSome(1))(IO(1)))\nretry(5)(attempt(assert(beSome(1))(IO(1))))\n```\n\n# IO and Retrying\nWhen testing asynchronous programs, especially UIs, it is not unusual to wait for a condition to become fulfilled.\nIn frameworks like **[specs2]** and **[scalatest]**, this feature is implemented as a special case with severe limitations\non composability.\n**xpct** treats retrying as a first class operation, allowing to retry a sequence of expectations with the same\nsemantics as strict operations:\n\n```scala\nfor {\n  text \u003c- {\n    for {\n      elem \u003c- getUiElement(5) must beASome[Text]\n      text \u003c- IO.pure(elem) must containText(\"Cancel\")\n    } yield text\n  }.retryEvery(10, 100.milliseconds)\n  _ \u003c- setUiElementText(6, text) must beRight\n} yield ()\n```\n\nAside from `cats.effect.IO`, arbitrary async effects can be used, as long as they implement the typeclass `EvalXp`:\n\n```scala\ntrait EvalXp[F[_]]\n{\n  def apply[A](fa: F[A]): A\n}\n```\n\nFor the retry operation, an instance of `cats.effect.Timer[F]` is required.\n\n# Test Frameworks\n\n## [kallikrein]\n**kallikrein** integration is the most seamless one, since it also focuses on `IO` programs.\n\nThe `xpct-klk` package contains instances of `Compile[Xp]` and `TestResult[XpResult]`.\nEither import the package `xpct.klk._`, mix in `XpctKlk` or subclass `XpctKlkTest[F, SbtResources]`.\n\n## [specs2]\nThe `xpct-specs2` package contains an instance of `AsResult[Xpct]`, which is sufficient for automatic conversion of\n`Xpct` values to specs2 `Fragment`s.\n\nA convenience trait `XpctSpec` is provided, which includes the implicit conversion to the extension class with `must`\nmethods.\n\n## [scalatest]\nThe `xpct-scalatest` package contains the trait `XpctSpec`, providing a helper function `xpct` that converts an `Xpct`\nto a `TestFailedException`.\n\n## [utest]\nThe `xpct-utest` package contains the trait `XpctSpec`, providing a helper function `xpct` that converts an `Xpct` to an\nexception.\n\n\n[cats-effect]: https://github.com/typelevel/cats-effect\n[kallikrein]: https://github.com/tek/kallikrein\n[specs2]: https://github.com/etorreborre/specs2\n[scalatest]: https://github.com/scalatest/scalatest\n[utest]: https://github.com/lihaoyi/utest\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftek%2Fxpct","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftek%2Fxpct","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftek%2Fxpct/lists"}