Crates.io | output-tracker |
lib.rs | output-tracker |
version | |
source | src |
created_at | 2025-02-15 10:58:05.25459+00 |
updated_at | 2025-02-15 10:58:05.25459+00 |
description | Track and assert state of dependencies in state-based tests without mocks |
homepage | |
repository | https://github.com/innoave/output-tracker.git |
max_upload_size | |
id | 1556673 |
Cargo.toml error: | TOML parse error at line 19, column 1 | 19 | autolib = false | ^^^^^^^ unknown field `autolib`, expected one of `name`, `version`, `edition`, `authors`, `description`, `readme`, `license`, `repository`, `homepage`, `documentation`, `build`, `resolver`, `links`, `default-run`, `default_dash_run`, `rust-version`, `rust_dash_version`, `rust_version`, `license-file`, `license_dash_file`, `license_file`, `licenseFile`, `license_capital_file`, `forced-target`, `forced_dash_target`, `autobins`, `autotests`, `autoexamples`, `autobenches`, `publish`, `metadata`, `keywords`, `categories`, `exclude`, `include` |
size | 0 |
Output-Tracker is a utility for writing state-based tests using nullables instead of mocks. It can track the state of dependencies which can then be asserted in the test.
Output-Tracker is created after the great article "Testing without Mocks" by James Shore and his concept of nullables.
Architectural patterns like Ports & Adapters, Hexagonal Architecture or A-Frame Architecture also help with easier testing of dependencies which are expensive to set up and/or lead to slow tests. In software that is designed following such architectural patterns can use Output-Tracker to track actions done by an outbound adapter which update the state. The sequence of actions can be asserted in a test.
Although the motivation for using an output-tracker is mainly testability, it can also be used in the production code for recording messages and state changes in a log.
Add output-tracker as a dependency to the Cargo.toml
file of your project:
[dependencies]
output-tracker = "0.1"
Making use of the output-tracker comprises the following steps:
OutputSubject
ObjectTracker
in the testuse output_tracker::non_threadsafe::{Error, OutputSubject, OutputTracker};
//
// Production code
//
#[derive(Debug, Clone, PartialEq)]
struct Message {
topic: String,
content: String,
}
struct Adapter {
output_subject: OutputSubject<Message>,
}
impl Adapter {
fn new() -> Self {
Self {
output_subject: OutputSubject::new(),
}
}
fn track_messages(&self) -> Result<OutputTracker<Message>, Error> {
self.output_subject.create_tracker()
}
fn send_message(&self, message: Message) {
// do some I/O
println!("sending message: '{} - {}'", message.topic, message.content);
// track that message was sent
// we ignore errors from the tracker here as it is not important for the business logic.
_ = self.output_subject.emit(message);
}
}
//
// Test
//
use assertor::*;
// this is a test method in a test module
// main() method is used here in the example so that it is compiled and run during doc-tests
fn main() {
let adapter = Adapter::new();
let tracker = adapter.track_messages().unwrap();
adapter.send_message(Message {
topic: "weather report".to_string(),
content: "it will be snowing tomorrow".to_string(),
});
adapter.send_message(Message {
topic: "no shadow".to_string(),
content: "keep your face to the sunshine and you cannot see a shadow".to_string(),
});
let tracker_output = tracker.output().unwrap();
assert_that!(tracker_output).contains_exactly_in_order(vec![
Message {
topic: "weather report".to_string(),
content: "it will be snowing tomorrow".to_string(),
},
Message {
topic: "no shadow".to_string(),
content: "keep your face to the sunshine and you cannot see a shadow".to_string(),
},
]);
}
There are integration tests that demonstrate the usage of this crate in a more involved way:
Example | Description |
---|---|
tests/basic_example.rs |
A basic example on how to use the output-tracker in an adapter like dependency to be tested. |
tests/threadsafe_example.rs |
Is the same as the basic example, but uses the threadsafe output-tracker instead of the non-threadsafe one. |
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. |
The output-tracker functionality is provided in a non-threadsafe variant and a threadsafe one. The different variants are gated behind crate features and can be activated as needed. The API of the two variants is interchangeable. That is the struct names and functions are identical for both variants. The module from which the structs are imported determines which variant is going to be used.
By default, only the non-threadsafe variant is compiled. One can activate only one variant or both variants if needed. The crate features and the variants which are activated by each feature are listed in the table below.
Crate feature | Variant | Rust module import |
---|---|---|
non-threadsafe |
non-threadsafe | use output_tracker::non_threadsafe::* |
threadsafe |
threadsafe | use output_tracker::threadsafe::* |