{"id":47005989,"url":"https://github.com/vangelov/saganario","last_synced_at":"2026-03-11T20:21:31.114Z","repository":{"id":65492390,"uuid":"85505861","full_name":"vangelov/saganario","owner":"vangelov","description":"A compact way to unit test redux sagas","archived":false,"fork":false,"pushed_at":"2017-05-15T05:55:57.000Z","size":24,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-02-15T01:28:39.281Z","etag":null,"topics":["generators","redux","redux-saga"],"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/vangelov.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-03-19T20:20:02.000Z","updated_at":"2019-07-15T13:06:50.000Z","dependencies_parsed_at":"2023-01-25T19:35:11.654Z","dependency_job_id":null,"html_url":"https://github.com/vangelov/saganario","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vangelov/saganario","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vangelov%2Fsaganario","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vangelov%2Fsaganario/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vangelov%2Fsaganario/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vangelov%2Fsaganario/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vangelov","download_url":"https://codeload.github.com/vangelov/saganario/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vangelov%2Fsaganario/sbom","scorecard":{"id":915903,"data":{"date":"2025-08-11","repo":{"name":"github.com/vangelov/saganario","commit":"8375556372009a636164e850d7858a32351a95a3"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","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":"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":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"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":"Code-Review","score":0,"reason":"Found 0/11 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":"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":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"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":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"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 'master'"],"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"}}]},"last_synced_at":"2025-08-24T20:58:32.647Z","repository_id":65492390,"created_at":"2025-08-24T20:58:32.647Z","updated_at":"2025-08-24T20:58:32.647Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30398846,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-11T18:46:22.935Z","status":"ssl_error","status_checked_at":"2026-03-11T18:46:17.045Z","response_time":84,"last_error":"SSL_read: 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":["generators","redux","redux-saga"],"created_at":"2026-03-11T20:21:30.358Z","updated_at":"2026-03-11T20:21:31.104Z","avatar_url":"https://github.com/vangelov.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# saganario\n\nWhen I first began writing unit tests for sagas using a standard framework such as `mocha`, I noticed\nthat there was too much boilerplate that had to be written and it was tedious to test different code paths. Another problem was tests checked every yielded value and its order which made them too dependent on the saga implementation. I looked into some of the libraries created specifically for testing sagas but they didn't seem much better.\n\n`Saganario` tries to provide a compact and powerful way to unit test redux sagas. It can be used with any testing framework.\n\n## Example\n\nLet's say we have the following saga:\n\n```javascript\nfunction* saga(x, y) {\n  try {\n    const action = yield take('SOME_ACTION');\n    yield put({ type: 'OTHER_ACTION', value: x + y + action.z });\n  } catch (e) {\n    yield put({ type: 'ERROR' });\n  }\n}\n```\n\nHere's a sample test for it:\n\n```javascript\nimport { prepareTest, expectNext, ifGiven } from 'saganario';\n\nconst test = prepareTest(saga, 1, 2, [\n  expectNext(take('SOME_ACTION')),\n\n  ifGiven({ type: 'SOME_ACTION', value: 2 }), [\n    expectNext(put({ type: 'OTHER_ACTION', value: 3 })),\n  ],\n  ifGiven(new Error()), [\n    expectNext(put({ type: 'ERROR' })),\n  ],\n]);\n```\n\nThe code above tests two different paths depending on whether there's an error.\n`Saganario` tests are made of nested arrays and look a bit like Lisp code.\n\n## Details\n\nThe tests can be recursively defined as follows:\n\n```\n\u003csaganarioTest\u003e ::= [\n  expect1,\n  expect2,\n  ...\n  expectN,\n\n  ifGiven(value1), \u003csaganarioTest\u003e,\n  ifGiven(value2), \u003csaganarioTest\u003e,\n  ...\n  ifGiven(value3), \u003csaganarioTest\u003e\n]\n```\n\nEssentially they represent a tree in which each node is a sequence of `expect` calls and each child node is yet another sequence of `expect` calls to which we transition by giving the argument of the `ifGiven` function to the saga. `Saganario` will run and check each path in the tree from the root to any of the leaves.\n\n### Running tests\n\nThe `prepareTest` function you saw earlier creates a function that you can execute and in case of an unmet expectation it throws an error. `prepareTest` takes the saga as the first argument, then any arguments the saga itself takes and then the test scenario.\n\nFor example you can use `saganario` with `mocha` like so:\n\n```javascript\ndescribe('saga', () =\u003e {\n  it('Does not throw in this scenario', () =\u003e {\n    const test = prepareTest(saga, 1, 2, [\n      ...\n    ]);\n\n    expect(test).to.not.throw();\n  });\n});\n```\n\n### Errors\n\nWhenever an expected value is not met `saganario` throws an error whose message contains: the expected value, the actual value and also the line in your test which caused the error.\n\nExample saga:\n\n```javascript\nfunction *saga() {\n  yield 'value1';\n  yield 'value2';\n}\n```\n\nTest:\n\n```javascript\n1. const test = prepareTest(saga, [\n2.   expectNext('value1');\n3.   expectNext('otherValue'); // on this line the expectation is not met\n4. ]);\n```\n\nAn error will be thrown with the following message `[expected]: value1; [actual]: otherValue (\u003cpath-to-your-file.js\u003e:3)`.\n\nIf you need to use the information from the message you can catch the error and get it in this way:\n\n```javascript\ntry {\n  test()\n} catch(error) {\n  const { expected, actual, line } = error.saganario;\n  // ...\n}\n```\n\n### Expecting values\n\nThere are several expectation types that you can use:\n\n#### `expectNext`\n\nThis is the most common type of expectation. It just checks if the next yielded value from the saga is what it's expected to be. You already saw examples of how to use it.\n\n#### `expectEventually`\n\nOften you don't care for all the values the saga yields, but only that it eventually emits a certain value. Take the following saga as an example:\n\n```javascript\nfunction *saga() {\n  const value = yield 'start';\n\n  if (value \u003e 0) {\n    yield 'step1';\n    yield 'step2';\n    yield 'step3';\n    yield 'end';\n  }\n\n  yield 'error';\n}\n```\nYou only care that it yields `'start'` at the beginning and than, at some point later, it emits `'end'`. You can write the following test:\n\n```javascript\nconst test = prepareTest(saga, [\n  expectNext('start');\n\n  ifGiven(100), [\n    expectEventually('end'),\n  ],\n  ifGiven(-1), [\n    expectNext('error'),\n  ]\n]);\n```\n\n*Note: If the expected value is not reached after 100 iterations of the generator an error will be thrown.*\n\n#### `expectEnd`\n\nThis type of expectation is used when you want to check that after the expected value is emitted, the generator ends.\n\nExample saga:\n\n```javascript\nfunction *saga() {\n  yield 'value1';\n  yield 'value2';\n}\n```\n\nTest:\n\n```javascript\nconst test = prepareTest(saga, [\n  expectNext('value1');\n  expectEnd('value2');\n]);\n```\n\n*Note: An error will be thrown if the generator is not done when `expectEnd` is called.*\n\n### Giving values\n\nThe only function you use for this is `ifGiven`. If the argument you set is not an instance of `Error` the value is given to the generator by using `generator.next(\u003cvalue\u003e)`, otherwise it's thrown as an exception in the generator using `generator.throw(\u003cvalue\u003e)`.\n\n## Installation\n\n`npm install --save saganario`\n\n## Example\n\nIn the `/examples` folder you will find several tests for sagas of different complexities. You can run them with: `npm run examples`.\n\n## Test\n\nTo run the tests execute: `npm test`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvangelov%2Fsaganario","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvangelov%2Fsaganario","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvangelov%2Fsaganario/lists"}