{"id":18513184,"url":"https://github.com/yeojz/redux-thunk-testing","last_synced_at":"2026-05-16T13:06:57.765Z","repository":{"id":34245947,"uuid":"173320074","full_name":"yeojz/redux-thunk-testing","owner":"yeojz","description":":nut_and_bolt: :camera: Test utility and Snapshot testing for complex and nested thunks","archived":false,"fork":false,"pushed_at":"2023-01-03T17:12:44.000Z","size":1247,"stargazers_count":1,"open_issues_count":15,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-03-25T09:04:08.957Z","etag":null,"topics":["async","middleware","redux","snapshot","testing","thunk"],"latest_commit_sha":null,"homepage":"https://yeojz.github.io/redux-thunk-testing","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/yeojz.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":"2019-03-01T14:57:33.000Z","updated_at":"2019-10-21T13:47:00.000Z","dependencies_parsed_at":"2023-01-15T05:37:51.114Z","dependency_job_id":null,"html_url":"https://github.com/yeojz/redux-thunk-testing","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/yeojz/redux-thunk-testing","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeojz%2Fredux-thunk-testing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeojz%2Fredux-thunk-testing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeojz%2Fredux-thunk-testing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeojz%2Fredux-thunk-testing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yeojz","download_url":"https://codeload.github.com/yeojz/redux-thunk-testing/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeojz%2Fredux-thunk-testing/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33103982,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-16T04:41:52.686Z","status":"ssl_error","status_checked_at":"2026-05-16T04:41:52.009Z","response_time":115,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["async","middleware","redux","snapshot","testing","thunk"],"created_at":"2024-11-06T15:36:45.949Z","updated_at":"2026-05-16T13:06:57.740Z","avatar_url":"https://github.com/yeojz.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# redux-thunk-testing\n\n\u003e Test utility and Snapshot testing for complex and nested redux-thunks\n\n[![npm][npm-badge]][npm-link]\n[![Build Status][circle-badge]][circle-link]\n[![Coverage Status][codecov-badge]][codecov-link]\n[![TypeScript Support][type-ts-badge]][type-ts-link]\n\n---\n\n\u003c!-- TOC depthFrom:2 --\u003e\n\n- [About](#about)\n- [Motivation](#motivation)\n- [Features](#features)\n- [Installation](#installation)\n- [Documentation](#documentation)\n- [Examples](#examples)\n  - [About the redux-thunk-readme-example](#about-the-redux-thunk-readme-example)\n- [Notes](#notes)\n  - [Tests are async](#tests-are-async)\n  - [`THUNK_ACTION` type](#thunkaction-type)\n  - [Flux Standard Action with Thunk payload](#flux-standard-action-with-thunk-payload)\n- [License](#license)\n\n\u003c!-- /TOC --\u003e\n\n## About\n\n`redux-thunk-testing` is a small utility/wrapper which aims to help in testing complex thunk actions\nand their side effects easily. The library runs through the action, executing all the thunks that\nare found, and subsequently provides utilities around the results for testing.\n\nConceptually, this allows you to set all your conditions, before running the action similarly to how\nit would execute in the context of your application.\n\n## Motivation\n\n[redux-thunk][redux-thunk-link] is a simple middleware for async and side-effects logic,\nand is nearly as ubiquitous as redux itself.\n\nHowever, as more complex flows and async actions are added, it becomes harder and more unwieldy to test.\nSome projects like [redux-saga][redux-saga-link] were created to help handle such issues, but\nthere might be situations where using such projects might not be a viable option due to\nconstraints and requirements of the project.\n\n## Features\n\n- Supports **Snapshot Testing**\n- Test / Assertion framework agnostic.\n  - `ActionTester` - for usage with `any assertion library`. (eg: Chai.js)\n  - `JestActionTester` - for usage with `jest.fn()`.\n- Supports `thunk.withExtraArgument`\n  - For more info about extraArguments, check out: [here][article-redux-thunk-readme] and [here][article-medium]\n- Supports actions that return `thunks`.\n- Supports `flux standard actions` with a thunk as the payload. [see notes](#flux-standard-action-with-thunk-payload)\n- TypeScript support ([definition file](https://unpkg.com/redux-thunk-testing/index.d.ts))\n\n## Installation\n\nInstall the library via:\n\n```bash\nnpm install redux-thunk-testing --save-dev\n```\n\n## Documentation\n\nQuick overview of functions / classes:\n\n- `class` ActionTester\n- `class` JestActionTester\n- `function` actionArraySnapshot\n- `function` actionSnapshot\n- `function` actionTesterSnapshot\n- `function` actionTracer\n- `function` actionTypes\n- `function` createActionRunner\n\nThe classes encapsulates the functions within this library.\n\nPlease refer to [Project Documentation][project-docs-link] for the full list\nof available methods.\n\n## Examples\n\n- [simple][example-simple]\n  - [snapshot][example-simple-snapshot]\n- [redux-thunk-readme-example][redux-thunk-readme-example]\n  - [snapshot][redux-thunk-readme-example-snapshot]\n\n### About the redux-thunk-readme-example\n\nThese are sample tests written for the \"make a sandwich\" code example from\n`redux-thunk` [README.md][redux-thunk-readme-link]. A copy of the example\nhas been copied over. Please see [examples/redux-thunk-readme/action.js][redux-thunk-readme-example-action-js].\n\n**Small change:**\n\nUnlike the original example, `fetchSecretSauce()` is assumed to be injected as an extraArgument\nto a thunk. _This is the only change._\n\n**Snippet:**\n\n```js\ntest('have enough money to make sandwiches for all', async () =\u003e {\n  // Setup testing conditions\n  // -------------------------------\n\n  // It is recommended to use thunk.withExtraArguments to inject\n  // your dependencies like apis as you can avoid module mocking by doing so.\n  const getState = jest.fn();\n  const extraArgs = {\n    api: {\n      fetchSecretSauce: jest.fn()\n    }\n  };\n\n  // Mocking the return values of api etc.\n  extraArgs.api.fetchSecretSauce.mockImplementation(() =\u003e\n    Promise.resolve('sauce')\n  );\n\n  getState.mockImplementation(() =\u003e ({\n    sandwiches: {\n      isShopOpen: true\n    },\n    myMoney: 100\n  }));\n\n  // Create the runner\n  // -------------------------------\n\n  // Setup tester + other thunk paramters\n  const tester = new JestActionTester(jest.fn());\n  // OR if you're not using jest.\n  const tester = new ActionTester();\n\n  // set the extra thunk arguments\n  tester.setArgs(getState, extraArgs);\n\n  // Run the action\n  // -------------------------------\n\n  await tester.dispatch(makeSandwichesForEverybody());\n\n  // Expectations\n  // -------------------------------\n\n  // Using Jest Snapshot\n  expect(tester.calls).toMatchSnapshot();\n\n  // Generating our own inline snapshot with\n  // expected function calls\n  const expected = actionArraySnapshot([\n    makeSandwichesForEverybody(),\n    makeASandwichWithSecretSauce('My Grandma'),\n    makeASandwich('My Grandma', 'sauce'),\n    makeASandwichWithSecretSauce('Me'),\n    makeASandwichWithSecretSauce('My wife'),\n    makeASandwich('Me', 'sauce'),\n    makeASandwich('My wife', 'sauce'),\n    makeASandwichWithSecretSauce('Our kids'),\n    makeASandwich('Our kids', 'sauce'),\n    withdrawMoney(42)\n  ]);\n\n  // Snapshot Testing (without depending on Jest)\n  expect(tester.toSnapshot()).toEqual(expected);\n\n  // Alternatively, just check the types\n  expect(tester.toTypes()).toEqual([\n    'THUNK_ACTION',\n    'THUNK_ACTION',\n    'MAKE_SANDWICH',\n    'THUNK_ACTION',\n    'THUNK_ACTION',\n    'MAKE_SANDWICH',\n    'MAKE_SANDWICH',\n    'THUNK_ACTION',\n    'MAKE_SANDWICH',\n    'WITHDRAW'\n  ]);\n});\n```\n\n## Notes\n\n### Tests are async\n\nThe runner treats all thunks as `async` methods / returning `promises`, even if you are\nreturning a basic type (eg: `boolean`, `string`).\n\nAs such, when using the tester, you should always use `await` or provide a `done` callback.\n\neg:\n\n```js\n// using async-await\nawait tester.dispatch(action());\n\n// using Promises\nPromise.resolve(tester.dispatch(action())) // dispatch always returns a promise\n  .then(() =\u003e {\n    // ...expectations here\n\n    done();\n  });\n\n// using a callback\ntester.dispatch(action(), () =\u003e {\n  // ...expectations here\n\n  done();\n});\n```\n\n### `THUNK_ACTION` type\n\nThis `action.type` is logged when the action being dispatched is a direct function / thunk and not\na functional payload of a \"Flux Standard Action\"\n\n```js\n// Given\nconst action = () =\u003e (dispatch, getState, extraArgs) =\u003e {\n  // code ...\n};\n\n// when testing\ntester.dispatch(action());\n\n// will resolve to (within the test suite)\ntester.dispatch({\n  type: 'THUNK_ACTION',\n  payload: action()\n});\n```\n\n### Flux Standard Action with Thunk payload\n\nNote: **This is optional**\n\nIf you don't want to see \"THUNK_ACTION\" in your tests, you\nmight want to consider dispatching FSA with a thunk payload.\n\ni.e.\n\n```js\n// Instead of\nfunction action() {\n  return dispatch =\u003e {\n    // code\n  };\n}\n\n// Try\nfunction action() {\n  return {\n    type: 'NAMED_ACTION',\n    payload: dispatch =\u003e {\n      // code\n    }\n  };\n}\n```\n\nHowever, in order to do the above, you will need the following middleware.\n\n```js\nconst fsaThunk = () =\u003e next =\u003e action =\u003e {\n  if (typeof action.payload === 'function') {\n    // Convert to a thunk action\n    return next(action.payload);\n  }\n  return next(action);\n};\n\n// Add it to your redux store.\nimport thunk from 'redux-thunk';\n\nconst store = createStore(\n  rootReducer,\n  applyMiddleware(\n    fsaThunk, // needs to be applied before the thunk\n    thunk\n  )\n);\n```\n\n## License\n\n`redux-thunk-testing` is [MIT licensed](https://github.com/yeojz/redux-thunk-testing/blob/master/LICENSE)\n\n[npm-badge]: https://img.shields.io/npm/v/redux-thunk-testing.svg?style=flat-square\n[npm-link]: https://www.npmjs.com/package/redux-thunk-testing\n[circle-badge]: https://img.shields.io/circleci/project/github/yeojz/redux-thunk-testing/master.svg?style=flat-square\n[circle-link]: https://circleci.com/gh/yeojz/redux-thunk-testing\n[type-ts-badge]: https://img.shields.io/badge/typedef-.d.ts-blue.svg?style=flat-square\u0026longCache=true\n[type-ts-link]: https://github.com/yeojz/redux-thunk-testing/tree/master/src/index.ts\n[codecov-badge]: https://img.shields.io/codecov/c/github/yeojz/redux-thunk-testing/master.svg?style=flat-square\n[codecov-link]: https://codecov.io/gh/yeojz/redux-thunk-testing\n[project-docs-link]: https://yeojz.github.io/redux-thunk-testing\n[redux-saga-link]: https://www.npmjs.com/package/redux-saga\n[example-simple]: https://github.com/yeojz/redux-thunk-testing/blob/master/examples/simple\n[example-simple-snapshot]: https://github.com/yeojz/redux-thunk-testing/blob/master/examples/simple/__snapshots__/actions.test.js.snap\n[redux-thunk-link]: https://www.npmjs.com/package/redux-thunk\n[redux-thunk-readme-link]: https://github.com/reduxjs/redux-thunk/blob/d5b6921037ea4ac414e8b6ba3398e4cd6287784c/README.md#Composition\n[redux-thunk-readme-example]: https://github.com/yeojz/redux-thunk-testing/blob/master/examples/redux-thunk-readme\n[redux-thunk-readme-example-action-js]: https://github.com/yeojz/redux-thunk-testing/blob/master/examples/redux-thunk-readme/actions.js\n[redux-thunk-readme-example-snapshot]: https://github.com/yeojz/redux-thunk-testing/blob/master/examples/redux-thunk-readme/__snapshots__/actions.test.js.snap\n[article-redux-thunk-readme]: https://github.com/reduxjs/redux-thunk#injecting-a-custom-argument\n[article-medium]: https://medium.com/@yeojz/redux-thunk-skipping-mocks-using-withextraargument-513d38d38554\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyeojz%2Fredux-thunk-testing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyeojz%2Fredux-thunk-testing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyeojz%2Fredux-thunk-testing/lists"}