diff --git a/ASCII/Case.hs b/ASCII/Case.hs
deleted file mode 100644
--- a/ASCII/Case.hs
+++ /dev/null
@@ -1,113 +0,0 @@
-{- |
-
-/Case/ is a property of letters. /A-Z/ are /upper case/ letters, and /a-z/ are /lower case/ letters. No other ASCII characters have case.
-
--}
-
-module ASCII.Case ( Case (..), letterCase, isCase, toCase ) where
-
-import ASCII.Char    ( Char (..) )
-import Data.Bool     ( Bool, otherwise )
-import Data.Data     ( Data )
-import Data.Eq       ( Eq )
-import Data.Function ( (.), ($) )
-import Data.Hashable ( Hashable )
-import Data.Ord      ( Ord, (<=), (>=) )
-import GHC.Generics  ( Generic )
-import Prelude       ( Enum, Bounded, Int, (+), (-) )
-import Text.Show     ( Show )
-import Data.Maybe    ( Maybe (..) )
-
-import qualified ASCII.Char as Char
-import qualified Data.Bool  as Bool
-
-{- $setup
-
->>> import Prelude
-
--}
-
-data Case =
-    UpperCase -- ^ The letters from 'CapitalLetterA' to 'CapitalLetterZ'.
-  | LowerCase -- ^ The letters from 'SmallLetterA' to 'SmallLetterZ'.
-
-deriving stock instance Eq Case
-
-deriving stock instance Ord Case
-
-deriving stock instance Enum Case
-
-deriving stock instance Bounded Case
-
-deriving stock instance Show Case
-
-deriving stock instance Data Case
-
-deriving stock instance Generic Case
-
-deriving anyclass instance Hashable Case
-
-{- | Determines whether a character is a letter, and if so, whether it is upper or lower case.
-
->>> map letterCase [CapitalLetterR, SmallLetterR, DollarSign]
-[Just UpperCase,Just LowerCase,Nothing]
-
--}
-
-letterCase :: Char -> Maybe Case
-letterCase x | isCase UpperCase x = Just UpperCase
-             | isCase LowerCase x = Just LowerCase
-             | otherwise          = Nothing
-
-{- | Determines whether a character is a letter of a particular case.
-
->>> map (isCase UpperCase) [CapitalLetterR,SmallLetterR,DollarSign]
-[True,False,False]
-
--}
-
-isCase :: Case -> Char -> Bool
-isCase c x = (Bool.&&) ( x >= a ) ( x <= z ) where (a, z) = az c
-
-az :: Case -> (Char, Char)
-az UpperCase = (CapitalLetterA, CapitalLetterZ)
-az LowerCase = (SmallLetterA, SmallLetterZ)
-
-{- | Maps a letter character to its upper/lower case equivalent.
-
->>> toCase UpperCase SmallLetterX
-CapitalLetterX
-
->>> toCase LowerCase CapitalLetterF
-SmallLetterF
-
-Characters that are already in the requested case are unmodified by this transformation.
-
->>> toCase UpperCase CapitalLetterA
-CapitalLetterA
-
-Characters that are not letters, such as exclamation mark, are unmodified by this transformation.
-
->>> toCase UpperCase ExclamationMark
-ExclamationMark
-
--}
-
-toCase :: Case -> Char -> Char
-toCase c x = if isCase (opposite c) x then changeCaseUnsafe c x else x
-
--- | Change a letter to the given case, assuming that the input character is a letter of the opposite case.
-
-changeCaseUnsafe :: Case -> Char -> Char
-changeCaseUnsafe c = charAsIntUnsafe (changeCaseInt c)
-
-changeCaseInt :: Case -> Int -> Int
-changeCaseInt LowerCase i = i + 32
-changeCaseInt UpperCase i = i - 32
-
-opposite :: Case -> Case
-opposite UpperCase = LowerCase
-opposite LowerCase = UpperCase
-
-charAsIntUnsafe :: (Int -> Int) -> (Char -> Char)
-charAsIntUnsafe f = Char.fromIntUnsafe . f . Char.toInt
diff --git a/ascii-case.cabal b/ascii-case.cabal
--- a/ascii-case.cabal
+++ b/ascii-case.cabal
@@ -1,7 +1,7 @@
 cabal-version: 2.0
 
 name: ascii-case
-version: 1.0.0.4
+version: 1.0.0.6
 synopsis: ASCII letter case
 category: Data, Text
 
@@ -18,8 +18,6 @@
 
 build-type: Simple
 
-tested-with: GHC == 9.0.1, GHC == 8.10.1, GHC == 8.8.1, GHC == 8.6.5, GHC == 8.4.3
-
 extra-source-files: changelog.txt
 
 source-repository head
@@ -35,6 +33,7 @@
     default-extensions: DeriveDataTypeable
     default-extensions: DeriveGeneric
     ghc-options: -Wall
+    hs-source-dirs: library
 
     build-depends: ascii-char ^>= 1.0
 
@@ -42,3 +41,14 @@
     build-depends: hashable   >= 1.2  && < 1.4
 
     exposed-modules: ASCII.Case
+
+test-suite test
+    type: exitcode-stdio-1.0
+    default-language: Haskell2010
+    default-extensions: NoImplicitPrelude
+    default-extensions: OverloadedStrings
+    default-extensions: QuasiQuotes
+    ghc-options: -Wall
+    hs-source-dirs: test
+    main-is: test.hs
+    build-depends: ascii-case, ascii-char, base
diff --git a/changelog.txt b/changelog.txt
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,4 @@
 1.0.0.0 - 2020-05-05 - Initial release
 1.0.0.2 - 2020-05-18 - Support GHC 8.10
 1.0.0.4 - 2021-02-10 - Support GHC 9.0
+1.0.0.6 - 2021-09-26 - Add a test suite
diff --git a/library/ASCII/Case.hs b/library/ASCII/Case.hs
new file mode 100644
--- /dev/null
+++ b/library/ASCII/Case.hs
@@ -0,0 +1,107 @@
+{- |
+
+/Case/ is a property of letters. /A-Z/ are /upper case/ letters, and /a-z/ are /lower case/ letters. No other ASCII characters have case.
+
+-}
+
+module ASCII.Case ( Case (..), letterCase, isCase, toCase ) where
+
+import ASCII.Char    ( Char (..) )
+import Data.Bool     ( Bool, otherwise )
+import Data.Data     ( Data )
+import Data.Eq       ( Eq )
+import Data.Function ( (.) )
+import Data.Hashable ( Hashable )
+import Data.Ord      ( Ord, (<=), (>=) )
+import GHC.Generics  ( Generic )
+import Prelude       ( Enum, Bounded, Int, (+), (-) )
+import Text.Show     ( Show )
+import Data.Maybe    ( Maybe (..) )
+
+import qualified ASCII.Char as Char
+import qualified Data.Bool  as Bool
+
+data Case =
+    UpperCase -- ^ The letters from 'CapitalLetterA' to 'CapitalLetterZ'.
+  | LowerCase -- ^ The letters from 'SmallLetterA' to 'SmallLetterZ'.
+
+deriving stock instance Eq Case
+
+deriving stock instance Ord Case
+
+deriving stock instance Enum Case
+
+deriving stock instance Bounded Case
+
+deriving stock instance Show Case
+
+deriving stock instance Data Case
+
+deriving stock instance Generic Case
+
+deriving anyclass instance Hashable Case
+
+{- | Determines whether a character is a letter, and if so, whether it is upper or lower case.
+
+>>> map letterCase [CapitalLetterR, SmallLetterR, DollarSign]
+[Just UpperCase,Just LowerCase,Nothing]
+
+-}
+
+letterCase :: Char -> Maybe Case
+letterCase x | isCase UpperCase x = Just UpperCase
+             | isCase LowerCase x = Just LowerCase
+             | otherwise          = Nothing
+
+{- | Determines whether a character is a letter of a particular case.
+
+>>> map (isCase UpperCase) [CapitalLetterR,SmallLetterR,DollarSign]
+[True,False,False]
+
+-}
+
+isCase :: Case -> Char -> Bool
+isCase c x = (Bool.&&) ( x >= a ) ( x <= z ) where (a, z) = az c
+
+az :: Case -> (Char, Char)
+az UpperCase = (CapitalLetterA, CapitalLetterZ)
+az LowerCase = (SmallLetterA, SmallLetterZ)
+
+{- | Maps a letter character to its upper/lower case equivalent.
+
+>>> toCase UpperCase SmallLetterX
+CapitalLetterX
+
+>>> toCase LowerCase CapitalLetterF
+SmallLetterF
+
+Characters that are already in the requested case are unmodified by this transformation.
+
+>>> toCase UpperCase CapitalLetterA
+CapitalLetterA
+
+Characters that are not letters, such as exclamation mark, are unmodified by this transformation.
+
+>>> toCase UpperCase ExclamationMark
+ExclamationMark
+
+-}
+
+toCase :: Case -> Char -> Char
+toCase c x = if isCase (opposite c) x then changeCaseUnsafe c x else x
+
+-- | Change a letter to the given case, assuming that the input character is a letter of the opposite case.
+
+changeCaseUnsafe :: Case -> Char -> Char
+changeCaseUnsafe c = charAsIntUnsafe (changeCaseInt c)
+
+changeCaseInt :: Case -> Int -> Int
+changeCaseInt LowerCase i = i + 32
+changeCaseInt UpperCase i = i - 32
+
+opposite :: Case -> Case
+opposite UpperCase = LowerCase
+opposite LowerCase = UpperCase
+
+charAsIntUnsafe :: (Int -> Int) -> (Char -> Char)
+charAsIntUnsafe f = Char.fromIntUnsafe . f . Char.toInt
diff --git a/test/test.hs b/test/test.hs
new file mode 100644
--- /dev/null
+++ b/test/test.hs
@@ -0,0 +1,54 @@
+module Main where
+
+import ASCII.Case
+
+import ASCII.Char (Char (..))
+
+import Control.Applicative (Applicative (..))
+import Control.Monad (Monad (..))
+import Data.Bool (Bool (..))
+import Data.Eq (Eq ((==)))
+import Data.Function ((.), ($))
+import Data.Functor (Functor (..))
+import Data.List (intercalate, map, null)
+import Data.Maybe (Maybe (..))
+import Data.Semigroup ((<>))
+import Numeric.Natural (Natural)
+import System.Exit (die)
+import System.IO (IO, putStrLn)
+import Text.Show (show)
+
+main :: IO ()
+main = dieIfFailures $ do
+    test 1 $ map letterCase [CapitalLetterR, SmallLetterR, DollarSign] == [Just UpperCase, Just LowerCase,Nothing]
+    test 2 $ map (isCase UpperCase) [CapitalLetterR, SmallLetterR, DollarSign] == [True, False, False]
+    test 3 $ toCase UpperCase SmallLetterX == CapitalLetterX
+    test 4 $ toCase LowerCase CapitalLetterF == SmallLetterF
+    test 5 $ toCase UpperCase CapitalLetterA == CapitalLetterA
+    test 6 $ toCase UpperCase ExclamationMark == ExclamationMark
+
+dieIfFailures :: Failures a -> IO a
+dieIfFailures (Failures fs x) =
+    if null fs
+        then do putStrLn "💯"; return x
+        else die $ intercalate " " (map (("🔥" <> ) . show) fs)
+
+type TestNumber = Natural
+
+test :: TestNumber -> Bool -> Failures ()
+test n t = Failures (if t then [] else [n]) ()
+
+data Failures a = Failures [TestNumber] a
+
+instance Functor Failures
+  where
+    fmap f (Failures a x) = Failures a (f x)
+
+instance Applicative Failures
+  where
+    pure x = Failures [] x
+    Failures a f <*> Failures b x = Failures (a <> b) (f x)
+
+instance Monad Failures
+  where
+    Failures a x >>= f = let Failures b y = f x in Failures (a <> b) y
