In my business logic I’ll usually mock out all IO by pulling the IO operation out into its own trait (or reuse an existing trait like
Write), that way you get faster and more reliable tests. It also helps decouple the components in your application.
Testing error handling and various edge cases can then be done by passing in a specially crafted mock (e.g. a
Write implementation which always errors) and seeing what happens.
If you’re testing IO code itself you pretty much need to do the operation (e.g. Rust
File::write()). Typically you’d execute the IO in a temporary environment (e.g.
tempfile or a db you spin up just for that test) where you don’t care about rogue tests breaking the world.
As an anecdote, one of the things we do at work is control actual mechanical machines and my initial reaction was to mock things out so the library is decoupled/insulated from the real world. Then one of the other engineers pointed out that the only way to make sure the machine moves when you hit a button is by actually hitting a button and seeing the machine move… That said, this is still done in a “safe” testing environment and not on customer machines.
Obviously you’re just using that as an example, but if
tempfile behaved differently on Windows to Linux I’d say its a bug in the
tempfile crate. Whatever you’re using to create a temporary environment/mock db/whatever should present a platform-independent interface to its users wherever possible.