diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,9 @@
+# Revision history for keyed-vals
+
+`keyed-vals-hspec-tests` uses [PVP Versioning][1].
+
+## 0.1.0.0 -- 2022-11-28
+
+* Initial version.
+
+[1]: https://pvp.haskell.org
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2022, Tim Emiola
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Tim Emiola nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,4 @@
+import Distribution.Simple
+
+
+main = defaultMain
diff --git a/keyed-vals-hspec-tests.cabal b/keyed-vals-hspec-tests.cabal
new file mode 100644
--- /dev/null
+++ b/keyed-vals-hspec-tests.cabal
@@ -0,0 +1,38 @@
+cabal-version:      3.0
+name:               keyed-vals-hspec-tests
+version:            0.1.0.0
+license:            BSD-3-Clause
+license-file:       LICENSE
+maintainer:         adetokunbo@emio.la
+author:             Tim Emiola
+tested-with:        GHC ==8.10.7 || ==9.0.2 || ==9.2.4 || ==9.4.1
+homepage:           https://github.com/adetokunbo/keyed-vals#readme
+bug-reports:        https://github.com/adetokunbo/keyed-vals/issues
+synopsis:
+  Validate a keyed-vals Handle
+
+description:
+  Provides a Hspec fixture for testing implementations of the abstract
+  Handle defined in the keyed-vals package
+
+category:           Data, Hspec
+build-type:         Simple
+extra-source-files:
+  ChangeLog.md
+
+source-repository head
+  type:     git
+  location: https://github.com/adetokunbo/keyed-vals.git
+
+library
+  exposed-modules:  Test.KeyedVals.Hspec
+  hs-source-dirs:   test
+  default-language: Haskell2010
+  ghc-options:      -Wall -Wincomplete-uni-patterns -fwarn-tabs
+  build-depends:
+    , base           >=4.11     && <5.0
+    , bytestring     >=0.10.8.2 && <0.11 || >=0.11.3.1 && <0.12
+    , containers     >=0.6.5    && <0.7
+    , keyed-vals     >=0.1      && <0.2
+    , benri-hspec    >=0.1      && <0.2
+    , hspec          >=2.7      && <2.12
diff --git a/test/Test/KeyedVals/Hspec.hs b/test/Test/KeyedVals/Hspec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/KeyedVals/Hspec.hs
@@ -0,0 +1,155 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_HADDOCK prune not-home #-}
+
+{- |
+Module      : Test.KeyedVals.Hspec
+Copyright   : (c) 2022 Tim Emiola
+Maintainer  : Tim Emiola <adetokunbo@emio.la>
+SPDX-License-Identifier: BSD3
+-}
+module Test.KeyedVals.Hspec (
+  -- * a test fixture
+  checkHandle,
+
+  -- * functions for extending the fixture
+  orThrowHandleErr,
+  throwHandleErr,
+  withGlobOf,
+
+  -- * setup/teardown hspec te+l fssts
+  setupFixture,
+  closeFixture,
+
+  -- * module re-eports
+  module Test.Hspec,
+  module Test.Hspec.Benri,
+) where
+
+import Control.Exception (throwIO)
+import Data.ByteString (ByteString)
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.Map.Strict as Map
+import KeyedVals.Handle
+import Numeric.Natural (Natural)
+import Test.Hspec
+import Test.Hspec.Benri
+
+
+checkHandle :: SpecWith (Handle IO)
+checkHandle = do
+  context "with simple values" $ do
+    it "should load ok" $ \h -> do
+      loadVal h key1 `endsRight` Just simple1
+
+    it "should update ok" $ \h -> do
+      endsRight_ $ saveVal h key1 "changed"
+      loadVal h key1 `endsRight` Just "changed"
+
+    it "should delete matching keys correctly " $ \h -> do
+      loadVal h key2 `endsRight` Just simple2
+      withGlobOf "*2" $ \patt -> do
+        endsRight_ $ deleteMatches h patt
+        loadVal h key2 `endsRight` Nothing
+
+    it "should delete ok" $ \h -> do
+      deleteKeys h (key1 :| []) `endsRight` ()
+      loadVal h key1 `endsRight` Nothing
+
+  context "with dict values" $ do
+    let mKey1Of h = loadFrom h mKey1
+
+    it "should load ok" $ \h -> do
+      loadKVs h mKey1 `endsRight` d1
+
+    checkLength mKey1 4
+
+    it "should update an indexed value ok" $ \h -> do
+      endsRight_ $ saveTo h mKey1 key1 "changed"
+      mKey1Of h key1 `endsRight` Just "changed"
+
+    checkLength mKey1 4
+
+    it "should add an indexed value ok" $ \h -> do
+      mKey1Of h "foo" `endsRight` Nothing
+      endsRight_ $ saveTo h mKey1 "foo" "bar"
+      mKey1Of h "foo" `endsRight` Just "bar"
+
+    checkLength mKey1 5
+
+    it "should delete indexed values ok" $ \h -> do
+      endsRight_ $ deleteKeysFrom h mKey1 (key1 :| [key2])
+      mKey1Of h "foo" `endsRight` Just "bar"
+      mKey1Of h key1 `endsRight` Nothing
+      mKey1Of h key2 `endsRight` Nothing
+
+    checkLength mKey1 3
+
+    it "should update the indexed values using a dict ok" $ \h -> do
+      endsRight_ $ updateKVs h mKey1 d2
+      mKey1Of h key1 `endsRight` Just simple3
+      mKey1Of h key2 `endsRight` Nothing
+      mKey1Of h key5 `endsRight` Just simple3
+
+    it "should fetch a subset of the indexed values as a dict ok" $ \h -> do
+      let want = Map.fromList [("foo", "bar"), (key5, simple3)]
+      loadSlice h mKey1 (AllOf ("foo" :| [key5])) `endsRight` want
+
+    it "should delete ok" $ \h -> do
+      endsRight_ $ deleteKeys h (mKey1 :| [])
+      loadKVs h mKey1 `endsRight` Map.empty
+      mKey1Of h "foo" `endsRight` Nothing
+
+    checkLength mKey1 0
+
+
+withGlobOf :: ByteString -> (Glob -> IO a) -> IO a
+withGlobOf x action = do
+  case mkGlob x of
+    Nothing -> throwIO $ userError "bad test pattern"
+    Just g -> action g
+
+
+checkLength :: Key -> Natural -> SpecWith (Handle IO)
+checkLength aKey n = context "and the reported length" $ do
+  it "should be correct " $ \h -> do
+    countKVs h aKey `endsRight` n
+
+
+setupFixture :: Handle IO -> IO (Handle IO)
+setupFixture h = do
+  orThrowHandleErr $ saveVal h key1 simple1
+  orThrowHandleErr $ saveVal h key2 simple2
+  orThrowHandleErr $ saveKVs h mKey1 d1
+  pure h
+
+
+closeFixture :: Handle IO -> IO ()
+closeFixture = close
+
+
+key1, key2, key3, key4, key5, mKey1 :: Key
+key1 = "a-simple-key-1"
+key2 = "a-simple-key-2"
+key3 = "another-key-1"
+key4 = "another_key-2"
+key5 = "yet-another-key"
+mKey1 = "a-map-key-1"
+
+
+simple1, simple2, simple3 :: Val
+simple1 = "a-simple-value-1"
+simple2 = "a-simple-value-2"
+simple3 = "a-simple-value-3"
+
+
+d1, d2 :: ValsByKey
+d1 = Map.fromList [(key1, simple1), (key2, simple2), (key3, simple1), (key4, simple2)]
+d2 = Map.fromList [(key1, simple3), (key5, simple3)]
+
+
+throwHandleErr :: HandleErr -> IO ()
+throwHandleErr = throwIO . userError . show
+
+
+orThrowHandleErr :: IO (Either HandleErr ()) -> IO ()
+orThrowHandleErr action = action >>= either throwHandleErr pure
