output-tracker 0.1.1

Track and assert state of dependencies in state-based tests without mocks
Documentation
use super::*;
use asserting::prelude::*;
use proptest::collection::vec;
use proptest::prelude::*;

#[test]
fn an_output_tracker_can_be_created_from_a_default_subject() {
    let subject = OutputSubject::<String>::default();
    let tracker = subject
        .create_tracker()
        .unwrap_or_else(|err| panic!("could not create output tracker: {err}"));

    assert_that!(tracker.output()).ok().is_empty();
}

#[test]
fn a_new_output_tracker_has_no_items_recorded() {
    let subject = OutputSubject::<String>::new();
    let tracker = subject
        .create_tracker()
        .unwrap_or_else(|err| panic!("could not create output tracker: {err}"));

    assert_that!(tracker.output()).ok().is_empty();
}

proptest! {
    #![proptest_config(ProptestConfig::with_cases(1000))]

    #[test]
    fn an_output_tracker_records_any_number_of_items_in_order(
        items in (0..=10_000_usize).prop_flat_map(|size| vec(any::<i64>(), size)),
    ) {
        let subject = OutputSubject::<i64>::new();
        let tracker = subject
            .create_tracker()
            .unwrap_or_else(|err| panic!("could not create output tracker: {err}"));

        for item in &items {
            subject.emit(*item)
                .unwrap_or_else(|err| panic!("could not emit item {item} on output subject: {err}"));
        }

        let output = tracker.output()
            .unwrap_or_else(|err| panic!("failed to read tracker output: {err}"));
        prop_assert_eq!(output, items);
    }

    #[test]
    fn the_output_of_a_tracker_can_be_read_several_times(
        items in (0..=10_000_usize).prop_flat_map(|size| vec(any::<i64>(), size)),
    ) {
        let subject = OutputSubject::<i64>::new();
        let tracker = subject
            .create_tracker()
            .unwrap_or_else(|err| panic!("could not create output tracker: {err}"));

        for item in &items {
            subject.emit(*item)
                .unwrap_or_else(|err| panic!("could not emit item {item} on output subject: {err}"));
        }

        let output = tracker.output()
            .unwrap_or_else(|err| panic!("failed to read tracker output: {err}"));
        prop_assert_eq!(&output, &items);
        let output = tracker.output()
            .unwrap_or_else(|err| panic!("failed to read tracker output: {err}"));
        prop_assert_eq!(&output, &items);
        let output = tracker.output()
            .unwrap_or_else(|err| panic!("failed to read tracker output: {err}"));
        prop_assert_eq!(&output, &items);
    }

    #[test]
    fn several_output_tracker_for_the_same_subject_track_same_items_independently(
        num_trackers in 2..=300_usize,
        items in (0..=500_usize).prop_flat_map(|size| vec(any::<i64>(), size)),
    ) {
        let subject = OutputSubject::<i64>::new();
        let trackers = (0..num_trackers).map(|i| {
            subject
            .create_tracker()
            .unwrap_or_else(|err| panic!("could not create output tracker {i}: {err}"))
        }).collect::<Vec<_>>();

        for item in &items {
            subject.emit(*item)
                .unwrap_or_else(|err| panic!("could not emit item {item} on output subject: {err}"));
        }

        for tracker in trackers {
            let output = tracker.output()
                .unwrap_or_else(|err| panic!("failed to read tracker output: {err}"));
            prop_assert_eq!(&output, &items);
        }
    }

    #[test]
    fn after_an_output_tracker_is_stopped_it_no_longer_tracks_items(
        items_before_stop in (0..=300_usize).prop_flat_map(|size| vec(any::<i64>(), size)),
        items_after_stop in (0..=50_usize).prop_flat_map(|size| vec(any::<i64>(), size)),
    ) {
        let subject = OutputSubject::<i64>::new();
        let tracker = subject
            .create_tracker()
            .unwrap_or_else(|err| panic!("could not create output tracker: {err}"));

        for item in &items_before_stop {
            subject.emit(*item)
                .unwrap_or_else(|err| panic!("could not emit item {item} on output subject: {err}"));
        }

        tracker.stop().unwrap_or_else(|err| panic!("failed to stop output tracker: {err}"));

        for item in &items_after_stop {
            subject.emit(*item)
                .unwrap_or_else(|err| panic!("could not emit item {item} on output subject: {err}"));
        }

        let output = tracker.output()
            .unwrap_or_else(|err| panic!("failed to read tracker output: {err}"));
        prop_assert_eq!(output, items_before_stop);
    }

    #[test]
    fn after_an_output_tracker_is_stopped_it_still_outputs_items_recorded_before(
        items_before_stop in (0..=300_usize).prop_flat_map(|size| vec(any::<i64>(), size)),
    ) {
        let subject = OutputSubject::<i64>::new();
        let tracker = subject
            .create_tracker()
            .unwrap_or_else(|err| panic!("could not create output tracker: {err}"));

        for item in &items_before_stop {
            subject.emit(*item)
                .unwrap_or_else(|err| panic!("could not emit item {item} on output subject: {err}"));
        }

        tracker.stop().unwrap_or_else(|err| panic!("failed to stop output tracker: {err}"));

        let output = tracker.output()
            .unwrap_or_else(|err| panic!("failed to read tracker output: {err}"));
        prop_assert_eq!(output, items_before_stop);
    }

    #[test]
    fn stopping_an_output_tracker_several_times_does_not_give_error_or_panic(
        items_before_stop in (0..=300_usize).prop_flat_map(|size| vec(any::<i64>(), size)),
    ) {
        let subject = OutputSubject::<i64>::new();
        let tracker = subject
            .create_tracker()
            .unwrap_or_else(|err| panic!("could not create output tracker: {err}"));

        for item in &items_before_stop {
            subject.emit(*item)
                .unwrap_or_else(|err| panic!("could not emit item {item} on output subject: {err}"));
        }

        assert_that!(tracker.stop()).is_ok();
        assert_that!(tracker.stop()).is_ok();
        assert_that!(tracker.stop()).is_ok();
    }

    #[test]
    fn after_clearing_an_output_tracker_its_output_is_empty(
        items in (0..=500_usize).prop_flat_map(|size| vec(any::<i64>(), size)),
    ) {
        let subject = OutputSubject::<i64>::new();
        let tracker = subject
            .create_tracker()
            .unwrap_or_else(|err| panic!("could not create output tracker: {err}"));

        for item in &items {
            subject.emit(*item)
                .unwrap_or_else(|err| panic!("could not emit item {item} on output subject: {err}"));
        }

        tracker.clear().unwrap_or_else(|err| panic!("failed to stop output tracker: {err}"));

        let output = tracker.output()
            .unwrap_or_else(|err| panic!("failed to read tracker output: {err}"));
        prop_assert!(output.is_empty(), "output of tracker should be empty, but is: {output:?}");
    }

    #[test]
    fn after_clearing_an_output_tracker_it_outputs_only_items_emitted_after_clearing(
        items_before_clear in (0..=50_usize).prop_flat_map(|size| vec(any::<i64>(), size)),
        items_after_clear in (0..=300_usize).prop_flat_map(|size| vec(any::<i64>(), size)),
    ) {
        let subject = OutputSubject::<i64>::new();
        let tracker = subject
            .create_tracker()
            .unwrap_or_else(|err| panic!("could not create output tracker: {err}"));

        for item in &items_before_clear {
            subject.emit(*item)
                .unwrap_or_else(|err| panic!("could not emit item {item} on output subject: {err}"));
        }

        tracker.clear().unwrap_or_else(|err| panic!("failed to stop output tracker: {err}"));

        for item in &items_after_clear {
            subject.emit(*item)
                .unwrap_or_else(|err| panic!("could not emit item {item} on output subject: {err}"));
        }

        let output = tracker.output()
            .unwrap_or_else(|err| panic!("failed to read tracker output: {err}"));
        prop_assert_eq!(output, items_after_clear);
    }
}