{"id":26124719,"url":"https://github.com/innoave/output-tracker","last_synced_at":"2025-04-13T15:26:24.500Z","repository":{"id":276692174,"uuid":"737677097","full_name":"innoave/output-tracker","owner":"innoave","description":"Track and assert state of dependencies in state-based tests without mocks","archived":false,"fork":false,"pushed_at":"2025-03-22T10:55:50.000Z","size":64,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-22T11:31:23.314Z","etag":null,"topics":["nullables","state-based-tests","tracking-state","without-mocks"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/innoave.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","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}},"created_at":"2024-01-01T03:24:42.000Z","updated_at":"2025-03-22T10:55:52.000Z","dependencies_parsed_at":"2025-03-10T16:50:05.605Z","dependency_job_id":"f6b59ce8-effe-462e-9ccf-47b870deee97","html_url":"https://github.com/innoave/output-tracker","commit_stats":null,"previous_names":["innoave/output-tracker"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/innoave%2Foutput-tracker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/innoave%2Foutput-tracker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/innoave%2Foutput-tracker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/innoave%2Foutput-tracker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/innoave","download_url":"https://codeload.github.com/innoave/output-tracker/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248734294,"owners_count":21153189,"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":["nullables","state-based-tests","tracking-state","without-mocks"],"created_at":"2025-03-10T16:49:57.328Z","updated_at":"2025-04-13T15:26:24.493Z","avatar_url":"https://github.com/innoave.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Output-Tracker\n\n[![crates.io][crates-badge]][crates-url]\n[![docs.rs][docs-badge]][docs-url]\n![MSRV][msrv-badge]\n[![code coverage][code-coverage-badge]][code-coverage-url]\n\nOutput-Tracker is a utility for writing state-based tests using [nullables] instead of mocks. It can\ntrack the state of dependencies which can then be asserted in the test.\n\n\u003e Output-Tracker is created after the great article\n\u003e [\"Testing without Mocks\" by James Shore][output-tracking]\n\u003e and his concept of [nullables].\n\nArchitectural patterns like Ports \u0026 Adapters, Hexagonal Architecture or A-Frame Architecture also\nhelp with easier testing of dependencies which are expensive to set up and/or lead to slow tests.\nIn software that is designed following such architectural patterns can use Output-Tracker to track\nactions done by an outbound adapter which update the state. The sequence of actions can be asserted\nin a test.\n\nAlthough the motivation for using an output-tracker is mainly testability, it can also be used\nin the production code for recording messages and state changes in a log.\n\n## Usage\n\nAdd output-tracker as a dependency to the `Cargo.toml` file of your project:\n\n```toml\n[dependencies]\noutput-tracker = \"0.1\"\n```\n\nMaking use of the output-tracker comprises the following steps:\n\n1. Equip an outbound adapter with an `OutputSubject`\n2. Create an `ObjectTracker` in the test\n3. Assert tracked messages/state changes for completeness and order (if appropriate)\n\n```rust\nuse output_tracker::non_threadsafe::{Error, OutputSubject, OutputTracker};\n\n//\n// Production code\n//\n\n#[derive(Debug, Clone, PartialEq)]\nstruct Message {\n    topic: String,\n    content: String,\n}\n\nstruct Adapter {\n    output_subject: OutputSubject\u003cMessage\u003e,\n}\n\nimpl Adapter {\n    fn new() -\u003e Self {\n        Self {\n            output_subject: OutputSubject::new(),\n        }\n    }\n\n    fn track_messages(\u0026self) -\u003e Result\u003cOutputTracker\u003cMessage\u003e, Error\u003e {\n        self.output_subject.create_tracker()\n    }\n\n    fn send_message(\u0026self, message: Message) {\n        // do some I/O\n        println!(\"sending message: '{} - {}'\", message.topic, message.content);\n\n        // track that message was sent\n        // we ignore errors from the tracker here as it is not important for the business logic.\n        _ = self.output_subject.emit(message);\n    }\n}\n\n//\n// Test\n//\nuse asserting::prelude::*;\n\n// this is a test method in a test module\n// main() method is used here in the example so that it is compiled and run during doc-tests\nfn main() {\n    let adapter = Adapter::new();\n\n    let tracker = adapter.track_messages().unwrap();\n\n    adapter.send_message(Message {\n        topic: \"weather report\".to_string(),\n        content: \"it will be snowing tomorrow\".to_string(),\n    });\n\n    adapter.send_message(Message {\n        topic: \"no shadow\".to_string(),\n        content: \"keep your face to the sunshine and you cannot see a shadow\".to_string(),\n    });\n\n    let tracker_output = tracker.output().unwrap();\n\n    assert_that!(tracker_output).contains_exactly(vec![\n        Message {\n            topic: \"weather report\".to_string(),\n            content: \"it will be snowing tomorrow\".to_string(),\n        },\n        Message {\n            topic: \"no shadow\".to_string(),\n            content: \"keep your face to the sunshine and you cannot see a shadow\".to_string(),\n        },\n    ]);\n}\n```\n\nThere are integration tests that demonstrate the usage of this crate in a more involved way:\n\n| Example                                                                        | Description                                                                                                              |\n|:-------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------|\n| [`tests/basic_example.rs`](tests/basic_example.rs)                             | A basic example on how to use the output-tracker in an adapter like dependency to be tested.                             |\n| [`tests/threadsafe_example.rs`](tests/threadsafe_example.rs)                   | Is the same as the basic example, but uses the threadsafe output-tracker instead of the non-threadsafe one.              |\n| [`tests/nullable_repository_example.rs`](tests/nullable_repository_example.rs) | A more advanced example showing how to use the nullable pattern and an output-tracker for testing a database repository. |\n\n## Threadsafe and non-threadsafe variants\n\nThe output-tracker functionality is provided in a non-threadsafe variant and a threadsafe one. The\ndifferent variants are gated behind crate features and can be activated as needed. The API of the\ntwo variants is interchangeable. That is the struct names and functions are identical for both\nvariants. The module from which the structs are imported determines which variant is going to be\nused.\n\nBy default, only the non-threadsafe variant is compiled. One can activate only one variant or both\nvariants if needed. The crate features and the variants which are activated by each feature are\nlisted in the table below.\n\n| Crate feature    | Variant        | Rust module import                      |\n|:-----------------|:---------------|:----------------------------------------|\n| `non-threadsafe` | non-threadsafe | `use output_tracker::non_threadsafe::*` |\n| `threadsafe`     | threadsafe     | `use output_tracker::threadsafe::*`     |\n\n\u003c!-- Badges and related URLs --\u003e\n\n[crates-badge]: https://img.shields.io/crates/v/output-tracker.svg\n\n[crates-url]: https://crates.io/crates/output-tracker\n\n[docs-badge]: https://docs.rs/output-tracker/badge.svg\n\n[docs-url]: https://docs.rs/output-tracker\n\n[msrv-badge]: https://img.shields.io/crates/msrv/output-tracker?color=chocolate\n\n[code-coverage-badge]: https://codecov.io/github/innoave/output-tracker/graph/badge.svg?token=o0w7R7J0Op\n\n[code-coverage-url]: https://codecov.io/github/innoave/output-tracker\n\n\n\u003c!-- External Links --\u003e\n\n[nullables]: https://www.jamesshore.com/v2/projects/nullables\n\n[output-tracking]: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#output-tracking\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finnoave%2Foutput-tracker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finnoave%2Foutput-tracker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finnoave%2Foutput-tracker/lists"}