{"id":38287139,"url":"https://github.com/rradczewski/expect-redux","last_synced_at":"2026-01-18T07:17:05.672Z","repository":{"id":8350328,"uuid":"58086079","full_name":"rradczewski/expect-redux","owner":"rradczewski","description":"expect-redux - better interaction testing for redux","archived":false,"fork":false,"pushed_at":"2026-01-13T21:44:31.000Z","size":5242,"stargazers_count":26,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-13T23:58:20.742Z","etag":null,"topics":["jest","mocha","react","redux","redux-observable","redux-saga","redux-thunk","testing"],"latest_commit_sha":null,"homepage":"https://ymmv.craftswerk.io/2018/11/expect-redux-better-interaction-tests-with-redux","language":"TypeScript","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/rradczewski.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2016-05-04T21:47:06.000Z","updated_at":"2026-01-13T21:44:34.000Z","dependencies_parsed_at":"2025-12-16T00:04:58.722Z","dependency_job_id":null,"html_url":"https://github.com/rradczewski/expect-redux","commit_stats":{"total_commits":173,"total_committers":4,"mean_commits":43.25,"dds":0.1502890173410405,"last_synced_commit":"826263047d2d1aeaaa4180e52cce069df81e4676"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/rradczewski/expect-redux","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rradczewski%2Fexpect-redux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rradczewski%2Fexpect-redux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rradczewski%2Fexpect-redux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rradczewski%2Fexpect-redux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rradczewski","download_url":"https://codeload.github.com/rradczewski/expect-redux/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rradczewski%2Fexpect-redux/sbom","scorecard":{"id":786511,"data":{"date":"2025-08-11","repo":{"name":"github.com/rradczewski/expect-redux","commit":"868dce7743cf4bd8882b08c2eefcf2e5d36ced68"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.7,"checks":[{"name":"Maintained","score":10,"reason":"30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/build.yaml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":4,"reason":"dependency not pinned by hash detected -- score normalized to 4","details":["Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yaml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/rradczewski/expect-redux/build.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yaml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/rradczewski/expect-redux/build.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yaml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/rradczewski/expect-redux/build.yaml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   1 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 1 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"13 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-4www-5p9h-95mh","Warn: Project is vulnerable to: GHSA-9gqv-wp59-fq42","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-rp65-9cf3-cjxr","Warn: Project is vulnerable to: GHSA-76c9-3jph-rj3q","Warn: Project is vulnerable to: GHSA-rhx6-c78j-4q9w","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-4v9v-hfq4-rm2v","Warn: Project is vulnerable to: GHSA-9jgg-88mc-972h"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-23T06:15:01.738Z","repository_id":8350328,"created_at":"2025-08-23T06:15:01.738Z","updated_at":"2025-08-23T06:15:01.738Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28492047,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T00:50:05.742Z","status":"online","status_checked_at":"2026-01-17T02:00:07.808Z","response_time":85,"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":["jest","mocha","react","redux","redux-observable","redux-saga","redux-thunk","testing"],"created_at":"2026-01-17T02:04:23.380Z","updated_at":"2026-01-17T02:04:23.456Z","avatar_url":"https://github.com/rradczewski.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Logo](https://raw.githubusercontent.com/rradczewski/expect-redux/HEAD/docs/logo.svg?sanitize=true)\n\n[![npm version](https://badge.fury.io/js/expect-redux.svg)](https://badge.fury.io/js/expect-redux)\n[![CircleCI](https://circleci.com/gh/rradczewski/expect-redux.svg?style=svg)](https://circleci.com/gh/rradczewski/expect-redux)\n[![Deps](https://david-dm.org/rradczewski/expect-redux.svg)](https://david-dm.org/rradczewski/expect-redux) [![DevDeps](https://david-dm.org/rradczewski/expect-redux/dev-status.svg)](https://david-dm.org/rradczewski/expect-redux)\n\n# expect-redux - better interaction testing for redux\n\n`expect-redux` is a testing library that enables you to write tests that verify the behavior of your business logic, no matter if you are using `redux-saga`, `redux-observable` or just `redux-thunk`. It provides a fluent DSL that makes writing tests with asynchronousness in mind a lot easier. \n\nHere's a simple example to give you an idea:\n\n```js\nit(\"should dispatch SUCCESSFULLY_CALLED on success\", () =\u003e {\n  const store = createStore(...);\n\n  fetch('/some-call-to-an-api')\n    .then(() =\u003e store.dispatch({ type: \"SUCCESSFULLY_CALLED\" }))\n\n  return expectRedux(store)\n    .toDispatchAnAction()\n    .ofType(\"SUCCESSFULLY_CALLED\")\n});\n```\n\nIt doesn't matter if the action is dispatched asynchronously or even if it already was dispatched when you call `expectRedux(store)...`, `expect-redux` records all previously dispatched actions as well as every action that will be dispatched.\n\nSee [`/examples`](examples/) for some example projects using different side-effect libraries that are tested with `expect-redux`, or checkout [this blogpost](https://ymmv.craftswerk.io/2018/11/expect-redux-better-interaction-tests-with-redux) where I compare testing strategies for several side-effect libraries and explain how `expect-redux` helps you to put them under test.\n\nA first version of `expect-redux` was developed for use in our projects at [@VaamoTech](https://twitter.com/VaamoTech) for [Vaamo](https://vaamo.de).\n\n## Installation\n\n```sh\nnpm install --save-dev expect-redux\n```\n\n## Usage\n\n`expect-redux` asserts the behavior of your effects, so it's best if you test your store like you would create it in your application code, not in isolation.\n\nIn order to record actions that are dispatched to it, your store only needs to be configured with a spy as an additional `storeEnhancer`:\n\n```js\n// store.js\nimport { createStore, compose } from \"redux\";\n\nexport const configureStore = (extraStoreEnhancers = []) =\u003e {\n  const storeEnhancers = [\n    ,\n    /* here goes e.g. applyMiddleware */ ...extraStoreEnhancers\n  ];\n\n  const store = createStore(reducer, compose(...storeEnhancers));\n\n  return store;\n};\n```\n\nWith that, you can add `storeSpy` from `expect-redux` as an `extraStoreEnhancer` in the setup of your tests:\n\n```js\nimport { configureStore } from \"./store.js\";\nimport { storeSpy } from \"expect-redux\";\n\nconst storeForTest = () =\u003e configureStore([storeSpy]);\n\ndescribe(\"your test\", () =\u003e {\n  const store = storeForTest();\n  // ...\n});\n```\n\n## API\n\n`expect-redux` supports both test-runners that support waiting for a `Promise` to resolve, such as `jest` or `mocha`, but also (thanks to [`chai-redux`](https://github.com/ScaCap/chai-redux) for the inspiration) supplying a `done` callback to `end(...)` as the last call to every assertion.\n\n```js\nit(\"works if you return a Promise\", () =\u003e {\n  return expectRedux(store)\n    .toDispatchAnAction()\n    .ofType(\"PROMISE\");\n});\n```\n\n```js\nit(\"works if you provide a `done` callback\", done =\u003e {\n  expectRedux(store)\n    .toDispatchAnAction()\n    .ofType(\"DONE\")\n    .end(done);\n});\n```\n\nWith the `Promise`-interface, it's easy to write async-await tests with assertions that wait for a certain action to be dispatched:\n\n```js\nit(\"works if you use async-await\", async () =\u003e {\n  // do something...\n\n  await expectRedux(store)\n    .toDispatchAnAction()\n    .ofType(\"WAITING FOR THIS\");\n\n  // do something else\n});\n```\n\n### Configuration\n\n#### `expectRedux.configure({ betterErrorMessagesTimeout: number | false })`\n\nFail if no expectation matched after `timeout` miliseconds. This is a workaround so you get a meaningful error message instead of a timeout error. Can go into the setup file as it's a global switch.\n\n![A screencast showing how the error messages look like](docs/example.gif)\n\n### Assertions on Actions\n\n`expect-redux` is built to assert behavior of side-effects, less the state that results in an action being dispatched. `toDispatchAnAction()` and `toNotDispatchAnAction()` encourage testing the reducer in isolation and instead testing the action producing side-effects.\n\n#### `expectRedux(store).toDispatchAnAction().ofType(type)`\n\nMatches by the passed `type` of an action only\n\n#### `expectRedux(store).toDispatchAnAction().matching(object)`\n\nMatches an action equal to the passed `object` (using [`R.equals`](http://ramdajs.com/docs/#equals))\n\n#### `expectRedux(store).toDispatchAnAction().matching(predicate)`\n\nMatches an action that satisfies the given `predicate`. predicate must be a function `Action =\u003e boolean`, e.g. `R.propEq('payload', 'foobar')`. Will not fail if the `predicate` returns `false`.\n\n#### `expectRedux(store).toDispatchAnAction().asserting(assertion)`\n\nMatches an action that won't let the given `assertion` throw an exception. Assertion must be a function `Action =\u003e any`, e.g. `action =\u003e expect(action.payload).toEqual(42)`. Will not fail if the `assertion` throws.\n\n#### `expectRedux(store).toDispatchAnAction().ofType(type).matching(predicate)`\n\nMatches an action that both is of type `type` and satisfies the given `predicate`. Like above, predicate must be a function `action =\u003e boolean`.\n\n#### `expectRedux(store).toDispatchAnAction().ofType(type).asserting(assertion)`\n\nMatches an action that both is of type `type` and does not let the given `assertion` throw. Assertion must be a function `Action =\u003e any`, e.g. `action =\u003e expect(action.payload).toEqual(42)`. Will not fail if the `assertion` throws.\n\n#### `expectRedux(store).toNotDispatchAnAction(timeout: number)...`\n\nWill negate all following predicates. If an action is dispatched that matches the predicates before `timeout` was reached, the test will fail.\n\nNote that this is a highly dangerous feature, as it relies on the `timeout` to prove that an action was not dispatched. If it takes longer for your side effect to dispatch the action, the test could misleadingly pass even though the action is ultimately dispatched to the store.\n\n### State related assertions\n\n#### `expectRedux(store).toHaveState().matching(expected: Object)`\n\nWill create an assertion that resolves once the store state matches the provided `expected` state.\n\n#### `expectRedux(store).toHaveState().withSubtree(selector: Object =\u003e mixed)...`\n\nWill select a subtree of the state before checking the following assertion. For example:\n\n```js\nexpectRedux(store)\n  .toHaveState()\n  .withSubtree(state =\u003e state.title)\n  .matching(\"Welcome to my website\");\n```\n\n## Similar or related libraries\n\n- [chai-redux](https://github.com/ScaCap/chai-redux) - a very similar assertion library with a `chai`-like DSL and timing-based assertions (first this action, then that action)\n- [redux-saga-test-plan](https://github.com/jfairbank/redux-saga-test-plan) - a testing framework similar to expect-redux, but tied closely to redux-saga\n- [redux-action-assertions](https://github.com/dmitry-zaets/redux-actions-assertions)\n- [redux-mock-store](https://github.com/arnaudbenard/redux-mock-store)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frradczewski%2Fexpect-redux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frradczewski%2Fexpect-redux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frradczewski%2Fexpect-redux/lists"}