diff --git a/hinotify.cabal b/hinotify.cabal
--- a/hinotify.cabal
+++ b/hinotify.cabal
@@ -1,5 +1,5 @@
 name:               hinotify
-version:            0.3.8
+version:            0.3.8.1
 build-type:         Simple
 synopsis:           Haskell binding to inotify
 description:
@@ -37,6 +37,7 @@
     build-depends: base, directory, hinotify
     hs-source-dirs: src tests
     main-is: test001-list-dir-contents.hs
+    other-modules: Utils
     ghc-options: -Wall
 
 test-suite test002
@@ -44,6 +45,7 @@
     build-depends: base, directory, hinotify
     hs-source-dirs: src tests
     main-is: test002-writefile.hs
+    other-modules: Utils
     ghc-options: -Wall
 
 test-suite test003
@@ -51,6 +53,7 @@
     build-depends: base, directory, hinotify
     hs-source-dirs: src tests
     main-is: test003-removefile.hs
+    other-modules: Utils
     ghc-options: -Wall
 
 test-suite test004
@@ -58,6 +61,7 @@
     build-depends: base, directory, hinotify
     hs-source-dirs: src tests
     main-is: test004-modify-file.hs
+    other-modules: Utils
     ghc-options: -Wall
 
 test-suite test005
@@ -65,4 +69,5 @@
     build-depends: base, directory, hinotify
     hs-source-dirs: src tests
     main-is: test005-move-file.hs
+    other-modules: Utils
     ghc-options: -Wall
diff --git a/tests/Utils.hs b/tests/Utils.hs
new file mode 100644
--- /dev/null
+++ b/tests/Utils.hs
@@ -0,0 +1,60 @@
+module Utils where
+
+import Control.Concurrent.Chan
+import Control.Exception
+
+import System.Directory
+import System.Environment
+import System.Exit
+
+import System.INotify
+
+testName :: IO String
+testName = do
+    n <- getProgName
+    return (n ++ "-playground")
+
+withTempDir :: (String -> IO a) -> IO a
+withTempDir f = do
+    path <- testName
+    bracket
+        ( createDirectory path >> return path )
+        ( removeDirectoryRecursive )
+        ( f )
+
+withWatch :: INotify -> [EventVariety] -> FilePath -> (Event -> IO ()) -> IO a -> IO a
+withWatch inot events path action f =
+    bracket
+        ( addWatch inot events path action )
+        removeWatch
+        ( const f )
+
+inTestEnviron :: [EventVariety] -> (String -> IO a) -> ([Event] -> IO b) -> IO b
+inTestEnviron events action f = do
+    withTempDir $ \testPath -> do
+        inot <- initINotify
+        chan <- newChan
+        withWatch inot events testPath (writeChan chan) $ do
+            _ <- action testPath
+            events' <- getChanContents chan
+            f events'
+
+(~=) :: Eq a => [a] -> [a] -> Bool
+[] ~= _ = True
+(x:xs) ~= (y:ys) = x == y && xs ~= ys
+_ ~= _ = False
+
+asMany :: [a] -> [a] -> [a]
+asMany xs ys = take (length xs) ys
+
+explainFailure :: Show a => [a] -> [a] -> String
+explainFailure expected reality = unlines $
+    [ "Expected:" ] ++
+    [ "> " ++ show x | x <- expected ] ++
+    [ "But got:" ] ++
+    [ "< " ++ show x | x <- asMany expected reality ]
+
+testFailure, testSuccess :: IO a
+testFailure = exitFailure 
+testSuccess = exitWith ExitSuccess
+
