Cóż, jeśli zamiast IO użyjesz monady, która symuluje I/O w idealnie kontrolowanym i ustalonym środowisku? Możesz łatwo przetestować te działania "IO" tak, jakby były czystymi funkcjami. Jest to idea na przykład IOSpec, która idzie jeszcze dalej, umożliwiając właśnie rodzaju efekt chcesz zezwolić w symulowanym IO określić, można napisać:
myFunction :: a -> b -> IOSpec (Teletype :+: IORefS)
myFunction x y = do
...
putStr (...)
ref <- newIORef ...
...
(dalekopis umożliwić funkcji zacisków, IORefS dla referencji) A następnie przetestuj swoją funkcję we właściwości quickcheck na przykład (zobacz moduł VM i runIOSpec) z pełną kontrolą nad wejściem i wyjściem lub nawet krok po kroku w GHCI. A jeśli działa poprawnie, po prostu zmień import, aby wprowadzić Test.IOSpec.Surrogate, który przedefiniuje IOSpec f jako synonim dla IO.