{"id":25302129,"url":"https://github.com/onatolich/saga-guide","last_synced_at":"2025-04-07T01:33:57.877Z","repository":{"id":57356020,"uuid":"182171204","full_name":"Onatolich/saga-guide","owner":"Onatolich","description":"Easy to use, framework agnostic saga testing utility.","archived":false,"fork":false,"pushed_at":"2021-12-08T19:24:09.000Z","size":24,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-15T03:19:11.080Z","etag":null,"topics":["redux-saga","test","testing","testing-tool","unit-testing"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/Onatolich.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-04-18T23:36:51.000Z","updated_at":"2021-12-08T19:24:12.000Z","dependencies_parsed_at":"2022-09-26T16:32:03.951Z","dependency_job_id":null,"html_url":"https://github.com/Onatolich/saga-guide","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Onatolich%2Fsaga-guide","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Onatolich%2Fsaga-guide/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Onatolich%2Fsaga-guide/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Onatolich%2Fsaga-guide/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Onatolich","download_url":"https://codeload.github.com/Onatolich/saga-guide/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247578488,"owners_count":20961264,"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":["redux-saga","test","testing","testing-tool","unit-testing"],"created_at":"2025-02-13T06:52:55.531Z","updated_at":"2025-04-07T01:33:57.854Z","avatar_url":"https://github.com/Onatolich.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# saga-guide\n\nEasy to use, framework agnostic saga testing utility.\nIt provides you with everything necessary to test your saga by wrapping it in a guided execution environment, and then track which actions was executed or error was thrown.\n\n## Getting started\n\n### Installation\n```sh\nnpm install --save-dev saga-guide\n```\n\n### Importing to your application\n```sh\nimport sagaGuide from 'saga-guide';\n// OR\nconst sagaGuide = require('saga-guide');\n```\n\n## API reference\n#### `sagaGuide(saga: Function, options?: Object): guidedSaga`\nCreates guided saga instance.\n1. `saga` - a saga you want to guide\n2. `options` - an optional object of saga execution options. It should match options list of [runSaga](https://redux-saga.js.org/docs/api/#runsagaoptions-saga-args) util of [redux-saga](https://github.com/redux-saga/redux-saga). However, there is a single `saga-guide` specific option:\n    * `state : any` - state which should be used for `select` effect.\n\n#### `guidedSaga.run(...args: Array\u003cany\u003e): void`\nRuns guided saga with passed arguments.\n```js\nfunction* saga(arg1, arg2) {...}\nconst guidedSaga = sagaGuide(saga);\nguidedSaga.run('arg1 value', 'arg2 value');\n```\nYou might run your guided saga as many times as you need. Before each run caught `error` and `dispatchState` will be reset.\n\n#### `guidedSaga.wasActionDispatched(action: Object): boolean`\nReturns true, if during last run was dispatched exactly the same action with exactly the same params (compared with deep-equal) as the passed one.\n\n```js\nconst action = actionCreator(params);\nexpect(guidedSaga.wasActionDispatched(action))\n    .toBeTruthy();\n```\n\n#### `guidedSaga.getAllDispatchedActionsByType(type: string): Array\u003cObject\u003e`\nReturns list of all dispatched actions during last run by passed action type.\nIt might be useful if you have to check how many times some particular action was called.\n```js\nexpect(guidedSaga.getAllDispatchedActionsByType('actionType'))\n    .toHaveLength(3);\n```\n\n#### `guidedSaga.getDispatchStack(): Array\u003cObject\u003e`\nReturns list of all dispatched actions during last run in order they were dispatched.\nCould be used to debug your tests by looking on an actual dispatch stack.\n\n```js\nexpect(guidedSaga.getDispatchStack())\n    .toHaveLength(3);\n```\n\n#### `guidedSaga.getResult(): any`\nReturns a result that was returned by guided saga during the last run if present.\n\n```js\nexpect(guidedSaga.getResult())\n  .toEqual('Expected return result');\n```\n\n#### `guidedSaga.getError(): ?Error`\nReturns an error that was thrown during the last run if present.\n\n```js\nexpect(guidedSaga.getError())\n    .toBe(saga.UnauthorisedException);\n\n// Or\nexpect(!!guidedSaga.getError())\n    .toBeTruthy();\n    \n// Or\nexpect(guidedSaga.getError())\n    .toMatchObject({ message: 'error message' });\n```\n\n#### `guidedSaga.setState(state: any): void`\nAllows you to set state which will be used to resolve `select` effect. (See [Resolving state](#resolving-state) section)\n```js\nguidedSaga.setState({ key: 'value' });\n```\n\n## Resolving state\nIf you are using `select` effect in your sagas, then you would like to mock your redux state for testing. For this you have 2 possibilities:\n- Define state with `getState` option during initialization\n```js\nsagaGuide(saga, {\n    getState: () =\u003e ({ key: 'value' }),\n});\n```\n- Define state with `guidedSaga` state management possibilities\n```js\nconst guidedSaga = sagaGuide(saga, {\n    state: { key: 'value' }, // This is optional\n});\n...\nguidedSaga.setState({ key: 'new value' });\n```\n\n## Custom expect matchers\n###### Note: Currently we are supporting only [Jest](https://jestjs.io/) custom matchers\n\n#### `toDispatchAction`\nAllows you to check whether some specific action was dispatched or not. \n```js\nexpect(guidedSaga).toDispatchAction(action);\n// Equals to\nexpect(guidedSaga.wasActionDispatched(action)).toBeTruthy();\n```\nHowever, in case of wrong assertion **toDispatchAction** matcher will tell you which actions was dispatched during the last run, so it will be easier to debug your test this way.\n\n#### `toDispatchActionType`\nAllows you to check if an action with specified type was dispatched at least once during last run.\nThe difference with [toDispatchAction](#todispatchaction) matcher is that `toDispatchActionType` will check only type without any additional payload.\n\nThis could be useful for `.not` assertions like:\n```js\nexpect(guidedSaga).toDispatchActionType(actionTypes.type);\nexpect(guidedSaga).not.toDispatchActionType(actionTypes.type);\n```\n\nOr you can pass an action instead of type. In this case matcher will automatically get passed action's type for an assertion:\n```js\nconst action = { type: actionTypes.type };\n\nexpect(guidedSaga).toDispatchActionType(action);\nexpect(guidedSaga).not.toDispatchActionType(action);\n```\n\n## Example\n\nConsider having next saga:\n```js\nimport { put, select } from 'redux-saga/effects';\nimport api from '../api';\nimport actions from '../actions';\n\nexport default function* saga(arg1) {\n    const storedData = yield select(state =\u003e state.data);\n\n    try {\n        const loadedData = yield api.loadSomeData(arg1, storedData);\n        yield put(actions.success(loadedData));\n    } catch (e) {\n        yield put(actions.error(e.message));\n        throw e;\n    }\n}\n```\n\nOur test will be as simple as this:\n```js\nimport sagaGuide from 'saga-guide';\nimport api from '../api';\nimport actions from '../actions';\nimport saga from './saga';\n\nconst arg1 = 'arg1 value';\nconst state = { data: 'some data' };\nconst responseData = 'resonse data';\n\nconst guidedSaga = sagaGuide(saga, { state });\n\nbeforeEach(() =\u003e {\n    jest.spyOn(api, 'loadSomeData').mockReturnValue(responseData);\n    guidedSaga.run(arg1);\n});\n\ntest('should load data from api with correct params', () =\u003e {\n    expect(api.loadSomeData)\n        .toHaveBeenCalledWith(arg1, state.data);\n});\n\ntest('should dispatch success action with data loaded from api', () =\u003e {\n    expect(guidedSaga)\n        .toDispatchAction(actions.success(responseData));\n});\n\ndescribe('error flow', () =\u003e {\n    const error = new Error('error message');\n\n    beforeEach(() =\u003e {\n        api.loadSomeData.mockImplementation(() =\u003e { throw error; });\n        guidedSaga.run(arg1);\n    });\n    \n    test('should dispatch error action with error message thrown', () =\u003e {\n        expect(guidedSaga)\n              .toDispatchAction(actions.error(error.message));\n    });\n    \n    test('should throw error further', () =\u003e {\n        expect(guidedSaga.getError())\n            .toBe(error);\n    });\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fonatolich%2Fsaga-guide","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fonatolich%2Fsaga-guide","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fonatolich%2Fsaga-guide/lists"}