The great thing about combinators is that you can easily embed business logic asynchronously. However I am yet to come up with a brilliant strategy for testing this cleanly.
- Sometimes I need to wait for the task to get scheduled so I can check whether the desired side effect happened
- Sometimes I need to wait to make sure the task ran and it decided not to do something
I want to do this in a way that will run as fast as my CPU is capable, and won't break under heavy load.
Here's a naive example (playground) where I want to make sure my filter worked properly:
let mut rt = Runtime::new().unwrap();
let counter = Arc::new(Mutex::new(0u32));
let counter_1 = counter.clone();
let (mut chan_tx, chan_rx) = mpsc::channel(64);
let task = chan_rx
.filter(|val| val % 2 == 1) // only allow odd numbers
.map(move |_| *counter_1.lock().unwrap() += 1)
.map_err(drop)
.for_each(ok);
rt.spawn(task);
let _ = chan_tx.try_send(1);
// This sleep is required to make sure the increment happened
thread::sleep(Duration::from_millis(50));
assert_eq!(*counter.lock().unwrap(), 1);
let _ = chan_tx.try_send(2);
// This sleep is required to make sure the increment _didn't_ happen
thread::sleep(Duration::from_millis(50));
assert_eq!(*counter.lock().unwrap(), 1);
let _ = chan_tx.try_send(3);
// This sleep is required to make sure the increment happened
thread::sleep(Duration::from_millis(50));
assert_eq!(*counter.lock().unwrap(), 2);
If I remove any of the three thread::sleep
s then the test does not work as intended.
I can think of some ways to work around this but they're all kind of horrible. Can anyone recommend general strategies for turning the above code into something testable?