diff --git a/hspec-dirstream.cabal b/hspec-dirstream.cabal
--- a/hspec-dirstream.cabal
+++ b/hspec-dirstream.cabal
@@ -1,6 +1,6 @@
 cabal-version: 1.18
 name: hspec-dirstream
-version: 0.5.0.0
+version: 1.0.0.0
 license: BSD3
 license-file: LICENSE
 copyright: Copyright: (c) 2018 Vanessa McHale
@@ -45,8 +45,7 @@
         filepath -any,
         hspec-core -any,
         system-filepath -any,
-        text -any,
-        bytestring -any
+        text -any
     
     if flag(development)
         ghc-options: -Werror
@@ -64,8 +63,7 @@
     build-depends:
         base -any,
         hspec-dirstream -any,
-        hspec -any,
-        bytestring -any
+        hspec -any
     
     if flag(development)
         
diff --git a/src/Test/Hspec/Dirstream.hs b/src/Test/Hspec/Dirstream.hs
--- a/src/Test/Hspec/Dirstream.hs
+++ b/src/Test/Hspec/Dirstream.hs
@@ -7,24 +7,30 @@
 module Test.Hspec.Dirstream
     ( -- Functions to generate a `Spec`
       testFiles
-    , testFilesIO
     , testFilesErr
-    , testFilesPredicate
-    , testFilesPredicateBS
-    , testPaths
+    , testFilesIO
+    , testFilesPure
+    -- * Configuration helper functions
+    , dirFiles
+    , allFiles
     -- * Helper functions for dealing with file extensions
     , F.extension
-    , Test.Hspec.Dirstream.hasExtension
+    , hasExtension
+    -- * Types and type synonyms
+    , FileProcessor (..)
+    , PathFilter
+    , Recursor
+    , PathProducer
+    , SafeSpecM
     ) where
 
-import qualified Data.ByteString.Lazy      as BSL
 import           Data.DirStream
 import           Data.Text                 (Text)
 import qualified Filesystem.Path.CurrentOS as F
 import           Pipes
 import qualified Pipes.Prelude             as P
 import           Pipes.Safe
-import           System.FilePath
+import           System.FilePath           hiding (hasExtension)
 import           Test.Hspec
 import           Test.Hspec.Core.Spec
 
@@ -39,8 +45,18 @@
 mapS :: (a -> SpecM () ()) -> Proxy () a y' y (SafeT (SpecM ())) r
 mapS = P.mapM_ . (lift .)
 
-paths :: MonadSafe m => String -> (F.FilePath -> Bool) -> Producer String m ()
-paths dir p = every (childOf path) >-> P.filter p >-> P.map F.encodeString
+type SafeSpecM = SafeT (SpecM ())
+type Recursor = F.FilePath -> ListT SafeSpecM F.FilePath
+type PathProducer = String -> PathFilter -> Producer String SafeSpecM ()
+
+dirFiles :: PathProducer
+dirFiles = getDirFiles childOf
+
+allFiles :: PathProducer
+allFiles = getDirFiles descendentOf
+
+getDirFiles :: Recursor -> PathProducer
+getDirFiles g dir p = every (g path) >-> P.filter p >-> P.map F.encodeString
     where path = F.decodeString dir
 
 -- | Helper function to generate a spec. The spec runs on the given directory,
@@ -64,38 +80,25 @@
           -> (F.FilePath -> Bool) -- ^ Filter on file extensions
           -> (String -> Either a String) -- ^ Function to process a file
           -> SpecWith ()
-testFiles dir p f = runSafeT $ runEffect $ paths dir p >-> mapS (testFile f)
-
--- | A version of the above where the return value can be any 'IO'.
-testFilesIO :: FilePath -> (F.FilePath -> Bool) -> (String -> IO String) -> SpecWith ()
-testFilesIO dir p f = runSafeT $ runEffect $ paths dir p >-> mapS (testFileIO f)
-
-testPaths :: FilePath -> (F.FilePath -> Bool) -> (FilePath -> IO ()) -> SpecWith ()
-testPaths dir p f = runSafeT $ runEffect $ paths dir p >-> mapS (testPathWorks f)
+testFiles = testHelper testFile dirFiles
 
--- | This function checks that each file returns a value satisfying the
--- predicate.
-testFilesPredicate :: (Show a, Eq a)
-                   => FilePath -- ^ Directory containing test data
-                   -> (F.FilePath -> Bool) -- ^ Filter on file extensions
-                   -> (String -> a) -- ^ Function to process the string
-                   -> (a -> Bool) -- ^ Predicate to check the result
-                   -> SpecWith ()
-testFilesPredicate dir p f pr = runSafeT $ runEffect $ paths dir p >-> mapS (testFilePredicate f pr)
+testHelper :: (a -> String -> SpecWith ()) -> PathProducer -> String -> PathFilter -> a -> SpecWith ()
+testHelper testFunction paths dir p = runSafeT . runEffect . (paths dir p >->) . mapS . testFunction
 
--- | Same as @testFilesPredicate@, but reads the file as a 'ByteString'.
-testFilesPredicateBS :: (Show a, Eq a)
-                    => FilePath
-                    -> (F.FilePath -> Bool)
-                    -> (BSL.ByteString -> a)
-                    -> (a -> Bool)
-                    -> SpecWith ()
-testFilesPredicateBS dir p f pr = runSafeT $ runEffect $ paths dir p >-> mapS (testFilePredicateBS f pr)
+testFilesIO :: FilePath
+            -> PathFilter
+            -> (String -> IO String)
+            -> SpecWith ()
+testFilesIO = testHelper testFileIO dirFiles
 
--- | Run an action on a file path.
-testPathWorks :: (FilePath -> IO ()) -> FilePath -> SpecWith ()
-testPathWorks fun path = it path $
-    fun path >>= (`shouldBe` ())
+-- | A very general means of testing files, where the check function is pure.
+testFilesPure :: (Show b, Eq b)
+           => FilePath
+           -> PathFilter
+           -> PathProducer
+           -> FileProcessor a b
+           -> SpecWith ()
+testFilesPure dir p paths = runSafeT . runEffect . (paths dir p >->) . mapS . testFilePure
 
 testFileIO :: (String -> IO String) -> String -> SpecWith ()
 testFileIO fun f = it f $ do
@@ -103,21 +106,25 @@
     expected <- readFile (replaceExtension f ".out")
     fun sample >>= (`shouldBe` expected)
 
+type PathFilter = F.FilePath -> Bool
+
 -- | This function simply tests that each file returns a 'Left' value and that
 -- the error message contained therein matches the contents of the appropriate
 -- file.
-testFilesErr :: (Show b, Eq b) => FilePath -> (F.FilePath -> Bool) -> (String -> Either String b) -> SpecWith ()
-testFilesErr dir p f = runSafeT $ runEffect $ paths dir p >-> mapS (testFileErr f)
+testFilesErr :: (Show b, Eq b) => FilePath -> PathFilter -> (String -> Either String b) -> SpecWith ()
+testFilesErr dir p f = runSafeT $ runEffect $ dirFiles dir p >-> mapS (testFileErr f)
 
-testFilePredicateBS :: (Eq a, Show a) => (BSL.ByteString -> a) -> (a -> Bool) -> String -> SpecWith ()
-testFilePredicateBS fun p f = it f $ do
-    sample <- BSL.readFile f
-    fun sample `shouldSatisfy` p
+data FileProcessor a b = FileProc { reader    :: FilePath -> IO a -- ^ A function to read files
+                                  , processor :: a -> b -- ^ A function to process files after they have been read
+                                  , check     :: b -> Bool -- ^ A predicate to check the output.
+                                  }
 
-testFilePredicate :: (Eq a, Show a) => (String -> a) -> (a -> Bool) -> String -> SpecWith ()
-testFilePredicate fun p f = it f $ do
-    sample <- readFile f
-    fun sample `shouldSatisfy` p
+testFilePure :: (Eq b, Show b) => FileProcessor a b -- ^ How to process a given file
+                                      -> String -- ^ File name
+                                      -> SpecWith ()
+testFilePure (FileProc rdr g p) f = it f $ do
+    sample <- rdr f
+    g sample `shouldSatisfy` p
 
 testFileErr :: (Eq b, Show b) => (String -> Either String b) -> String -> SpecWith ()
 testFileErr fun f = it f $ do
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,7 +1,5 @@
 {-# LANGUAGE OverloadedStrings #-}
 
-import           Control.Monad
-import qualified Data.ByteString.Lazy as BSL
 import           Test.Hspec
 import           Test.Hspec.Dirstream
 
@@ -11,9 +9,5 @@
             testFiles "test/data" (hasExtension "hs") ((pure :: a -> Either String a) . tail)
     describe "testFilesIO" $
             testFilesIO "test/data" (hasExtension "hs") (pure . tail)
-    describe "testFilesPredicate" $
-            testFilesPredicate "test/data" (hasExtension "hs") tail ((>= 0) . length)
-    describe "testFilesPredicateBS" $
-            testFilesPredicateBS "test/data" (hasExtension "hs") id ((>= 0) . BSL.length)
-    describe "testPaths" $
-            testPaths "test/data" (hasExtension "hs") (void . readFile)
+    describe "testFilesGeneral" $
+            testFilesPure "test/data" (hasExtension "hs") allFiles (FileProc readFile tail ((>= 0) . length))
