diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,13 @@
 # Revision history for collection-json
 
+## 1.1.0.0  -- 2017-11-05
+
+* minimize produced JSON in ToJSON instances
+* attempt to ensure RFC compliance with hspec
+* add more robust URI testing
+* add nix development environment (shell.nix, default.nix, collection-json.nix)
+* change test suite to hspec
+
 ## 1.0.1.0  -- 2017-08-08
 
 * increase aeson upper bound to 1.2
diff --git a/collection-json.cabal b/collection-json.cabal
--- a/collection-json.cabal
+++ b/collection-json.cabal
@@ -1,5 +1,5 @@
 name:                collection-json
-version:             1.0.1.0
+version:             1.1.0.0
 synopsis:            Collection+JSON—Hypermedia Type Tools
 
 description:
@@ -24,9 +24,6 @@
   , LICENSE
   , README.md
   , Setup.hs
-  , test/Data/CollectionJSON/Tests.hs
-  , test/Internal/Network/URI/Tests.hs
-  , test/Main.hs
 
 source-repository head
   type:     git
@@ -35,20 +32,22 @@
 
 library
   default-language: Haskell2010
-  hs-source-dirs:   src
 
-  exposed-modules:
-      Data.CollectionJSON
-
   ghc-options: -Wall -fwarn-tabs -fwarn-monomorphism-restriction
                -fwarn-unused-do-bind
 
+  hs-source-dirs:
+      src
+
+  exposed-modules:
+      Data.CollectionJSON
+
   other-modules:
-      Internal.Network.URI
+      Internal.Network.URI.JSON
 
   build-depends:
       aeson       >= 0.8 && < 1.2
-    , base        >= 4.6 && < 4.11
+    , base        >= 4.6 && < 4.12
     , network-uri == 2.6.*
     , text        == 1.2.*
 
@@ -56,26 +55,35 @@
       OverloadedStrings
     , RecordWildCards
 
-test-suite Properties
+test-suite collection-json-tests
   default-language: Haskell2010
-  hs-source-dirs:   src test
-
   type:             exitcode-stdio-1.0
-  main-is:          Main.hs
+  main-is:          Spec.hs
 
   ghc-options: -Wall -fwarn-tabs -fwarn-monomorphism-restriction
                -fwarn-unused-do-bind
 
+  hs-source-dirs:
+      src
+    , test
+
   other-modules:
       Data.CollectionJSON
-      Data.CollectionJSON.Tests
+    , Data.CollectionJSON.Arbitrary
+    , Data.CollectionJSONSpec
+    , Internal.Network.URI.Arbitrary
+    , Internal.Network.URI.ArbitrarySpec
+    , Internal.Network.URI.JSON
+    , Internal.Network.URI.JSONSpec
 
-      Internal.Network.URI
-      Internal.Network.URI.Tests
+  build-tool-depends:
+      hspec-discover:hspec-discover == 2.4.*
 
   build-depends:
       aeson                >= 0.8 && < 1.2
-    , base                 >= 4.6 && < 4.11
+    , base                 >= 4.6 && < 4.12
+    , bytestring           == 0.10.*
+    , hspec                == 2.4.*
     , network-uri          == 2.6.*
     , QuickCheck           == 2.9.*
     , quickcheck-instances == 0.3.*
@@ -85,4 +93,3 @@
   other-extensions:
       OverloadedStrings
     , RecordWildCards
-    , TemplateHaskell
diff --git a/src/Data/CollectionJSON.hs b/src/Data/CollectionJSON.hs
--- a/src/Data/CollectionJSON.hs
+++ b/src/Data/CollectionJSON.hs
@@ -15,10 +15,12 @@
 module Data.CollectionJSON where
 
 import Data.Aeson ((.=), (.:?), (.!=), (.:), FromJSON (parseJSON), object, ToJSON (toJSON), withObject)
+import Data.Functor ((<$>))
+import Data.Maybe (catMaybes)
 import Data.Text (Text)
-import Network.URI (URI)
+import Network.URI (nullURI, URI)
 
-import Internal.Network.URI ()
+import Internal.Network.URI.JSON ()
 
 -- * Core Data Types
 
@@ -39,7 +41,7 @@
     v <- c .: "collection"
 
     cVersion  <- v .:? "version"  .!= "1.0"
-    cHref     <- v .:  "href"
+    cHref     <- v .:? "href"     .!= nullURI
     cLinks    <- v .:? "links"    .!= []
     cItems    <- v .:? "items"    .!= []
     cQueries  <- v .:? "queries"  .!= []
@@ -50,16 +52,16 @@
 
 instance ToJSON Collection where
   toJSON Collection{..} = object
-    [ "collection" .= object
-      [ "version"  .= cVersion
-      , "href"     .= cHref
-      , "links"    .= cLinks
-      , "items"    .= cItems
-      , "queries"  .= cQueries
-      , "template" .= cTemplate
-      , "error"    .= cError
+    [ "collection" .= object (catMaybes
+      [ Just $ "version"  .= cVersion
+      , Just $ "href"     .= cHref
+      , if null cLinks   then Nothing else Just $ "links"    .= cLinks
+      , if null cItems   then Nothing else Just $ "items"    .= cItems
+      , if null cQueries then Nothing else Just $ "queries"  .= cQueries
+      , (.=) "template" <$> cTemplate
+      , (.=) "error"    <$> cError
       ]
-    ]
+    ) ]
 
 {-|
 A link to a related resource (not necessarily an
@@ -89,12 +91,12 @@
     return Link{..}
 
 instance ToJSON Link where
-  toJSON Link{..} = object
-    [ "href"   .= lHref
-    , "rel"    .= lRel
-    , "name"   .= lName
-    , "render" .= lRender
-    , "prompt" .= lPrompt
+  toJSON Link{..} = object $ catMaybes
+    [ Just $ "href"   .= lHref
+    , Just $ "rel"    .= lRel
+    , (.=) "name"   <$> lName
+    , (.=) "render" <$> lRender
+    , (.=) "prompt" <$> lPrompt
     ]
 
 -- | An element in the 'Collection'
@@ -114,10 +116,10 @@
     return Item{..}
 
 instance ToJSON Item where
-  toJSON Item{..} = object
-    [ "href"  .= iHref
-    , "data"  .= iData
-    , "links" .= iLinks
+  toJSON Item{..} = object $ catMaybes
+    [ Just $ "href"  .= iHref
+    , if null iData  then Nothing else Just $ "data"  .= iData
+    , if null iLinks then Nothing else Just $ "links" .= iLinks
     ]
 
 {-|
@@ -155,12 +157,12 @@
     return Query{..}
 
 instance ToJSON Query where
-  toJSON Query{..} = object
-    [ "href"   .= qHref
-    , "rel"    .= qRel
-    , "name"   .= qName
-    , "prompt" .= qPrompt
-    , "data"   .= qData
+  toJSON Query{..} = object $ catMaybes
+    [ Just $ "href"   .= qHref
+    , Just $ "rel"    .= qRel
+    , (.=) "name"   <$> qName
+    , (.=) "prompt" <$> qPrompt
+    , if null qData then Nothing else Just $ "data"   .= qData
     ]
 
 -- | A fillable template for creation of a new object in the 'Collection'.
@@ -196,10 +198,10 @@
     return Error{..}
 
 instance ToJSON Error where
-  toJSON Error{..} = object
-    [ "title"   .= eTitle
-    , "code"    .= eCode
-    , "message" .= eMessage
+  toJSON Error{..} = object $ catMaybes
+    [ (.=) "title"   <$> eTitle
+    , (.=) "code"    <$> eCode
+    , (.=) "message" <$> eMessage
     ]
 
 -- | Contents of a 'Collection' 'Item'.
@@ -218,10 +220,10 @@
     return Datum{..}
 
 instance ToJSON Datum where
-  toJSON Datum{..} = object
-    [ "name"   .= dName
-    , "value"  .= dValue
-    , "prompt" .= dPrompt
+  toJSON Datum{..} = object $ catMaybes
+    [ Just $ "name"   .= dName
+    , (.=) "value"  <$> dValue
+    , (.=) "prompt" <$> dPrompt
     ]
 
 -- * Type Conversion
diff --git a/src/Internal/Network/URI.hs b/src/Internal/Network/URI.hs
deleted file mode 100644
--- a/src/Internal/Network/URI.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-
-{-# LANGUAGE OverloadedStrings #-}
-
-{-|
-Module      : Internal.Network.URI
-Description : URI Helper Functions
-Copyright   : (c) Alex Brandt, 2017
-License     : MIT
-
-URI utility functions that don't belong anywhere else.
--}
-module Internal.Network.URI where
-
-import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON), withText)
-import Data.Text (unpack)
-import Network.URI (parseURIReference, URI)
-
-instance FromJSON URI where
-  parseJSON = withText "URI" $ \ v ->
-    case parseURIReference (unpack v) of
-      Nothing -> fail "invalid URI"
-      Just x  -> return x
-
-instance ToJSON URI where
-  toJSON = toJSON . show
diff --git a/src/Internal/Network/URI/JSON.hs b/src/Internal/Network/URI/JSON.hs
new file mode 100644
--- /dev/null
+++ b/src/Internal/Network/URI/JSON.hs
@@ -0,0 +1,23 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+{-# LANGUAGE OverloadedStrings #-}
+
+{-|
+Module      : Internal.Network.URI.JSON
+Description : URI FromJSON and ToJSON Instances
+Copyright   : (c) Alex Brandt, 2017
+License     : MIT
+
+URI Instances for FromJSON and ToJSON
+-}
+module Internal.Network.URI.JSON where
+
+import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON), withText)
+import Data.Text (unpack)
+import Network.URI (parseURIReference, URI, uriToString)
+
+instance FromJSON URI where
+  parseJSON = withText "URI" $ maybe (fail "invalid URI") return . parseURIReference . unpack
+
+instance ToJSON URI where
+  toJSON u = toJSON $ uriToString id u ""
diff --git a/test/Data/CollectionJSON/Arbitrary.hs b/test/Data/CollectionJSON/Arbitrary.hs
new file mode 100644
--- /dev/null
+++ b/test/Data/CollectionJSON/Arbitrary.hs
@@ -0,0 +1,59 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+{-|
+Module      : Data.CollectionJSON.Arbitrary
+Description : Arbitrary Instances for Data.CollectionJSON
+Copyright   : (c) Alex Brandt, 2017
+License     : MIT
+
+Arbitrary instances for "Data.CollectionJSON".
+-}
+module Data.CollectionJSON.Arbitrary where
+
+import Control.Applicative ((<$>), (<*>))
+import Data.Text (pack)
+import Test.QuickCheck (Arbitrary (arbitrary))
+import Test.QuickCheck.Instances ()
+
+import Data.CollectionJSON
+import Internal.Network.URI.Arbitrary ()
+
+instance Arbitrary Collection where
+  arbitrary = Collection (pack "1.0") <$> arbitrary
+                                      <*> arbitrary
+                                      <*> arbitrary
+                                      <*> arbitrary
+                                      <*> arbitrary
+                                      <*> arbitrary
+
+instance Arbitrary Link where
+  arbitrary = Link <$> arbitrary
+                   <*> arbitrary
+                   <*> arbitrary
+                   <*> arbitrary
+                   <*> arbitrary
+
+instance Arbitrary Item where
+  arbitrary = Item <$> arbitrary
+                   <*> arbitrary
+                   <*> arbitrary
+
+instance Arbitrary Query where
+  arbitrary = Query <$> arbitrary
+                    <*> arbitrary
+                    <*> arbitrary
+                    <*> arbitrary
+                    <*> arbitrary
+
+instance Arbitrary Template where
+  arbitrary = Template <$> arbitrary
+
+instance Arbitrary Error where
+  arbitrary = Error <$> arbitrary
+                    <*> arbitrary
+                    <*> arbitrary
+
+instance Arbitrary Datum where
+  arbitrary = Datum <$> arbitrary
+                    <*> arbitrary
+                    <*> arbitrary
diff --git a/test/Data/CollectionJSON/Tests.hs b/test/Data/CollectionJSON/Tests.hs
deleted file mode 100644
--- a/test/Data/CollectionJSON/Tests.hs
+++ /dev/null
@@ -1,111 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE TemplateHaskell #-}
-
-{-|
-Module      : Data.CollectionJSON.Tests
-Description : Tests for Data.CollectionJSON
-Copyright   : (c) Alex Brandt, 2017
-License     : MIT
-
-Tests for "Data.CollectionJSON".
--}
-module Data.CollectionJSON.Tests (runTests) where
-
-import Data.Aeson (decode, encode)
-import Data.Maybe (fromJust)
-import Data.Text (pack)
-import Test.Invariant ((<=>))
-import Test.QuickCheck (Arbitrary (arbitrary), forAllProperties, maxSize, stdArgs, quickCheckWithResult)
-import Test.QuickCheck.Instances ()
-
-import Data.CollectionJSON
-import Internal.Network.URI.Tests ()
-
-prop_id_c :: Collection -> Bool
-prop_id_c = fromJust . decode . encode <=> id
-
-prop_id_l :: Link -> Bool
-prop_id_l = fromJust . decode . encode <=> id
-
-prop_id_i :: Item -> Bool
-prop_id_i = fromJust . decode . encode <=> id
-
-prop_id_q :: Query -> Bool
-prop_id_q = fromJust . decode . encode <=> id
-
-prop_id_t :: Template -> Bool
-prop_id_t = fromJust . decode . encode <=> id
-
-prop_id_e :: Error -> Bool
-prop_id_e = fromJust . decode . encode <=> id
-
-prop_id_d :: Datum -> Bool
-prop_id_d = fromJust . decode . encode <=> id
-
-return []
-runTests :: IO Bool
-runTests = $forAllProperties $ quickCheckWithResult $ stdArgs {maxSize = 50}
-
-instance Arbitrary Collection where
-  arbitrary =
-    do let cVersion = pack "1.0"
-       cHref     <- arbitrary
-       cLinks    <- arbitrary
-       cItems    <- arbitrary
-       cQueries  <- arbitrary
-       cTemplate <- arbitrary
-       cError    <- arbitrary
-
-       return Collection{..}
-
-instance Arbitrary Link where
-  arbitrary =
-    do lHref   <- arbitrary
-       lRel    <- arbitrary
-       lName   <- arbitrary
-       lRender <- arbitrary
-       lPrompt <- arbitrary
-
-       return Link{..}
-
-instance Arbitrary Item where
-  arbitrary =
-    do iHref  <- arbitrary
-       iData  <- arbitrary
-       iLinks <- arbitrary
-
-       return Item{..}
-
-instance Arbitrary Query where
-  arbitrary =
-    do qHref   <- arbitrary
-       qRel    <- arbitrary
-       qName   <- arbitrary
-       qPrompt <- arbitrary
-       qData   <- arbitrary
-
-       return Query{..}
-
-instance Arbitrary Template where
-  arbitrary =
-    do tData <- arbitrary
-       
-       return Template{..}
-
-instance Arbitrary Error where
-  arbitrary =
-    do eTitle   <- arbitrary
-       eCode    <- arbitrary
-       eMessage <- arbitrary
-
-       return Error{..}
-
-instance Arbitrary Datum where
-  arbitrary =
-    do dName   <- arbitrary
-       dValue  <- arbitrary
-       dPrompt <- arbitrary
-
-       return Datum{..}
diff --git a/test/Data/CollectionJSONSpec.hs b/test/Data/CollectionJSONSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Data/CollectionJSONSpec.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+{-|
+Module      : Data.CollectionJSONSpec
+Description : Tests for Data.CollectionJSON
+Copyright   : (c) Alex Brandt, 2017
+License     : MIT
+
+Tests for "Data.CollectionJSON".
+-}
+module Data.CollectionJSONSpec (main, spec) where
+
+import Data.Aeson (decode, encode)
+import Data.Maybe (fromJust, isJust, isNothing)
+import Network.URI (parseURIReference)
+import Test.Hspec (context, describe, hspec, it, shouldBe, Spec)
+import Test.Hspec.QuickCheck (modifyMaxSize, prop)
+import Test.Invariant ((<=>))
+
+import qualified Data.ByteString.Lazy as BL (ByteString)
+
+import Data.CollectionJSON
+import Data.CollectionJSON.Arbitrary ()
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "application/vnd.collection+json" $ modifyMaxSize (const 25) $
+    do describe "RFC compliance (http://amundsen.com/media-types/collection/format/)" $
+         do prop "'Template' decode JSON string: \"{}\"" $ isJust (decode "{}" :: Maybe Template)
+            prop "'Collection' decode JSON string: \"{\"collection\":{}}\"" $ isJust (decode "{\"collection\":{}}" :: Maybe Collection) 
+
+       describe "common parse errors" $
+         prop "'Collection' errors on \"{}\"" $ isNothing (decode "{}" :: Maybe Collection)
+
+       describe "properties" $
+         context "fromJust . decode . encode == id" $
+           do prop "Datum"      (fromJust . decode . encode <=> id :: Datum -> Bool)
+              prop "Error"      (fromJust . decode . encode <=> id :: Error -> Bool)
+              prop "Template"   (fromJust . decode . encode <=> id :: Template -> Bool)
+              prop "Query"      (fromJust . decode . encode <=> id :: Query -> Bool)
+              prop "Item"       (fromJust . decode . encode <=> id :: Item -> Bool)
+              prop "Link"       (fromJust . decode . encode <=> id :: Link -> Bool)
+              prop "Collection" (fromJust . decode . encode <=> id :: Collection -> Bool)
+
+       describe "JSON Missing Keys" $
+         do context "decode minimal JSON strings" $
+              do prop "Datum"      $ isJust (decode mDatum :: Maybe Datum)
+                 prop "Error"      $ isJust (decode mError :: Maybe Error)
+                 prop "Template"   $ isJust (decode mTemplate :: Maybe Template)
+                 prop "Query"      $ isJust (decode mQuery :: Maybe Query)
+                 prop "Item"       $ isJust (decode mItem :: Maybe Item)
+                 prop "Link"       $ isJust (decode mLink :: Maybe Link)
+                 prop "Collection" $ isJust (decode mCollection :: Maybe Collection)
+
+            context "encode minimal data to JSON" $
+              do it "Datum" $
+                   encode (Datum "name" Nothing Nothing) `shouldBe` mDatum
+
+                 it "Error" $
+                   encode (Error Nothing Nothing Nothing) `shouldBe` mError
+
+                 it "Template" $
+                   encode (Template []) `shouldBe` mTemplate
+
+                 it "Query" $
+                   encode (Query eURI "item" Nothing Nothing []) `shouldBe` mQuery
+
+                 it "Item" $
+                   encode (Item eURI [] []) `shouldBe` mItem
+
+                 it "Link" $
+                   encode (Link eURI "item" Nothing Nothing Nothing) `shouldBe` mLink
+
+                 it "Collection" $
+                   encode (Collection "1.0" eURI [] [] [] Nothing Nothing) `shouldBe` mCollection
+
+  where mCollection = "{\"collection\":{\"href\":\"http://example.com\",\"version\":\"1.0\"}}" :: BL.ByteString
+        mLink       = "{\"href\":\"http://example.com\",\"rel\":\"item\"}" :: BL.ByteString
+        mItem       = "{\"href\":\"http://example.com\"}" :: BL.ByteString
+        mQuery      = "{\"href\":\"http://example.com\",\"rel\":\"item\"}" :: BL.ByteString
+        mTemplate   = "{\"data\":[]}" :: BL.ByteString
+        mError      = "{}" :: BL.ByteString
+        mDatum      = "{\"name\":\"name\"}" :: BL.ByteString
+
+        eURI = fromJust $ parseURIReference "http://example.com"
diff --git a/test/Internal/Network/URI/Arbitrary.hs b/test/Internal/Network/URI/Arbitrary.hs
new file mode 100644
--- /dev/null
+++ b/test/Internal/Network/URI/Arbitrary.hs
@@ -0,0 +1,187 @@
+{-# LANGUAGE RecordWildCards #-}
+
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+{-|
+Module      : Internal.Network.URI.Arbitrary
+Description : Arbitrary Instances for Network.URI
+Copyright   : (c) Alex Brandt, 2017
+License     : MIT
+
+Arbitrary instances for "Network.URI".
+-}
+module Internal.Network.URI.Arbitrary () where
+
+import Control.Applicative ((<$>), (<*>))
+import Control.Monad (replicateM)
+import Data.List (intercalate)
+import Network.URI (URI (..), URIAuth (..))
+import Test.QuickCheck (Arbitrary (arbitrary), choose, elements, Gen, listOf, listOf1, oneof, suchThat)
+
+instance Arbitrary URI where
+  arbitrary =
+    do uriScheme    <- scheme
+       uriAuthority <- arbitrary :: Gen (Maybe URIAuth)
+       uriPath      <- path (null uriScheme) $ maybe True emptyAuthority uriAuthority
+       uriQuery     <- oneof [query, return ""]
+       uriFragment  <- oneof [fragment, return ""]
+
+       return URI {..}
+    where emptyAuthority URIAuth{..} = all null [uriUserInfo, uriRegName, uriPort]
+
+instance Arbitrary URIAuth where
+  arbitrary = URIAuth <$> userinfo
+                      <*> host `suchThat` (not . null)
+                      <*> port
+
+-- * RFC 3986 Generators
+--
+--   Some generators are handled by the 'Arbitrary' instances above, and others
+--   are folded into symbols that are preceeded or followed by identifying
+--   tokens.
+
+scheme :: Gen String
+scheme =
+  do a <- alpha
+     r <- listOf $ oneof [alpha, digit, elements ['+', '-', '.']]
+     return $ a : (r ++ ":")
+
+userinfo :: Gen String
+userinfo =
+  do u <- concat <$> userinfo'
+     if null u
+        then return ""
+        else return $ u ++ "@"
+  where userinfo' = listOf $ oneof [ replicateM 1 $ oneof [unreserved, subDelims, return ':']
+                                   , percentEncoded
+                                   ]
+
+host :: Gen String
+host = oneof [ ipLiteral
+             , ipv4Address
+             , regName
+             ]
+
+port :: Gen String
+port =
+  do p <- listOf digit
+     if null p
+        then return ""
+        else return $ ':':p
+
+ipLiteral :: Gen String
+ipLiteral =
+  do x <- oneof [ ipv6Address
+                --, ipvFuture
+                ]
+     return $ "[" ++ x ++ "]"
+
+{- TODO Check that "Network.URI" implements this correctly.
+ipvFuture :: Gen String
+ipvFuture =
+  do h <- hexdig
+     o <- oneof [ unreserved, subDelims, return ':' ]
+     return ['v', h, '.', o]
+-}
+
+ipv6Address :: Gen String
+ipv6Address = concat <$> oneof [ sequence                        [b 6, ls32]
+                               , sequence           [return "::", b 5, ls32]
+                               , sequence      [h16, return "::", b 4, ls32]
+                               , sequence [b 1, h16, return "::", b 3, ls32]
+                               , sequence [b 2, h16, return "::", b 2, ls32]
+                               , sequence [b 3, h16, return "::", b 1, ls32]
+                               , sequence [b 4, h16, return "::",      ls32]
+                               , sequence [b 5, h16, return "::",       h16]
+                               , sequence [b 6, h16, return "::"]
+                               ]
+  where b n = fmap concat $ replicateM n $ fmap (++ ":") h16 :: Gen String
+
+h16 :: Gen String
+h16 = replicateM 4 hexdig
+
+ls32 :: Gen String
+ls32 = oneof [ intercalate ":" <$> replicateM 2 h16
+             , ipv4Address
+             ]
+
+ipv4Address :: Gen String
+ipv4Address = intercalate "." <$> replicateM 4 decOctet
+
+decOctet :: Gen String
+decOctet = (show :: Int -> String) <$> choose (0, 255)
+
+regName :: Gen String
+regName = fmap concat $ listOf $ oneof [ replicateM 1 unreserved
+                                       , percentEncoded
+                                       , replicateM 1 subDelims
+                                       ]
+
+path :: Bool -> Bool -> Gen String
+path emptyScheme emptyURIAuth = if emptyURIAuth
+                                   then oneof [ pathAbsolute
+                                              , if emptyScheme then pathNoScheme else pathRootless
+                                              , return ""
+                                              ]
+                                   else pathAbEmpty
+
+pathAbEmpty :: Gen String
+pathAbEmpty = concat <$> listOf ((('/':) . concat) <$> listOf pchar)
+
+pathAbsolute :: Gen String
+pathAbsolute = ('/':) <$> oneof [return "", pathRootless]
+
+pathNoScheme :: Gen String
+pathNoScheme = concat <$> sequence [segment1nc, pathAbEmpty]
+
+pathRootless :: Gen String
+pathRootless = concat <$> sequence [ concat <$> listOf1 pchar
+                                   , pathAbEmpty
+                                   ]
+
+segment1nc :: Gen String
+segment1nc = oneof [ replicateM 1 unreserved
+                   , percentEncoded
+                   , replicateM 1 subDelims
+                   , replicateM 1 $ return '@'
+                   ] 
+
+pchar :: Gen String
+pchar = oneof [ replicateM 1 unreserved
+              , percentEncoded
+              , replicateM 1 subDelims
+              , replicateM 1 $ return ':'
+              , replicateM 1 $ return '@'
+              ]
+
+query :: Gen String
+query = fmap (('?':) . concat) $ listOf $ oneof [ pchar
+                                                , return "/"
+                                                , return "?"
+                                                ]
+
+fragment :: Gen String
+fragment = fmap (('#':) . concat) $ listOf $ oneof [ pchar
+                                                   , return "/"
+                                                   , return "?"
+                                                   ]
+
+percentEncoded :: Gen String
+percentEncoded = ('%':) <$> replicateM 2 hexdig
+
+unreserved :: Gen Char
+unreserved = oneof [ alpha, digit, elements ['-', '.', '_', '~']]
+
+subDelims :: Gen Char
+subDelims = elements ['!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=']
+
+-- * RFC 2234 Generators
+
+alpha :: Gen Char
+alpha = elements $ ['a'..'z'] ++ ['A'..'Z']
+
+digit :: Gen Char
+digit = elements ['0'..'9']
+
+hexdig :: Gen Char
+hexdig = oneof [digit, elements ['A'..'F']]
diff --git a/test/Internal/Network/URI/ArbitrarySpec.hs b/test/Internal/Network/URI/ArbitrarySpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Internal/Network/URI/ArbitrarySpec.hs
@@ -0,0 +1,25 @@
+{-|
+Module      : Internal.Network.URI.ArbitrarySpec
+Description : Tests for Internal.Network.URI.Arbitrary
+Copyright   : (c) Alex Brandt, 2017
+License     : MIT
+
+Tests for "Internal.Network.URI.Arbitrary".
+-}
+module Internal.Network.URI.ArbitrarySpec (main, spec) where
+
+import Network.URI (isURIReference, parseURIReference, uriToString)
+import Test.Hspec (describe, hspec, Spec)
+import Test.Hspec.QuickCheck (prop)
+
+import Internal.Network.URI.Arbitrary ()
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "properties" $
+    do prop "isURIReference (uriToString id u \"\")" $ \ u -> isURIReference (uriToString id u "")
+
+       prop "Just u == parseURIReference (uriToString id u \"\")" $ \ u -> Just u == parseURIReference (uriToString id u "")
diff --git a/test/Internal/Network/URI/JSONSpec.hs b/test/Internal/Network/URI/JSONSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Internal/Network/URI/JSONSpec.hs
@@ -0,0 +1,27 @@
+{-|
+Module      : Internal.Network.URI.JSONSpec
+Description : Tests for Internal.Network.URI.JSON
+Copyright   : (c) Alex Brandt, 2017
+License     : MIT
+
+Tests for "Internal.Network.URI.JSON".
+-}
+module Internal.Network.URI.JSONSpec (main, spec) where
+
+import Data.Aeson (decode, encode)
+import Data.Maybe (fromJust)
+import Network.URI (URI)
+import Test.Hspec (describe, hspec, Spec)
+import Test.Hspec.QuickCheck (prop)
+import Test.Invariant ((<=>))
+
+import Internal.Network.URI.Arbitrary ()
+import Internal.Network.URI.JSON ()
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "properties" $
+    prop "fromJust . decode . encode == id" (fromJust . decode . encode <=> id :: URI -> Bool)
diff --git a/test/Internal/Network/URI/Tests.hs b/test/Internal/Network/URI/Tests.hs
deleted file mode 100644
--- a/test/Internal/Network/URI/Tests.hs
+++ /dev/null
@@ -1,48 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-
-{-|
-Module      : Internal.Network.URI.Tests
-Description : URI Arbitrary Instances
-Copyright   : (c) Alex Brandt, 2017
-License     : MIT
-
-A collection of 'Arbitrary' instances for 'URI'.
--}
-module Internal.Network.URI.Tests where
-
-import Control.Applicative ((<$>))
-import Data.Maybe (fromJust)
-import Network.URI (parseURIReference, URI)
-import Test.QuickCheck (Arbitrary (arbitrary), elements)
-
--- | Not a general implementation.
---
---   This implementation just returns a random URI from the
---   @application/vnd.collection+json@ examples.
-instance Arbitrary URI where
-  arbitrary = fromJust . parseURIReference <$> elements uris
-    where uris = [ "http://example.org/friends/"
-                 , "http://example.org/friends/"
-                 , "http://example.org/friends/rss"
-                 , "http://example.org/friends/jdoe"
-                 , "http://examples.org/blogs/jdoe"
-                 , "http://examples.org/images/jdoe"
-                 , "http://example.org/friends/msmith"
-                 , "http://examples.org/blogs/msmith"
-                 , "http://examples.org/images/msmith"
-                 , "http://example.org/friends/rwilliams"
-                 , "http://examples.org/blogs/rwilliams"
-                 , "http://examples.org/images/rwilliams"
-                 , "http://example.org/friends/search"
-                 , "http://example.org/friends/"
-                 , "http://example.org/friends/rss"
-                 , "http://example.org/friends/?queries"
-                 , "http://example.org/friends/?template"
-                 , "http://example.org/friends/jdoe"
-                 , "http://examples.org/blogs/jdoe"
-                 , "http://examples.org/images/jdoe"
-                 , "http://example.org/friends/"
-                 , "http://example.org/friends/search"
-                 , "http://example.org/friends/"
-                 , "http://example.org/friends/"
-                 ]
diff --git a/test/Main.hs b/test/Main.hs
deleted file mode 100644
--- a/test/Main.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-{-|
-Module      : Main
-Copyright   : (c) Alex Brandt, 2017
-License     : MIT
-
-"Main" Module for collection-json property tests.
--}
-module Main where
-
-import Control.Applicative ((<$>))
-import System.Exit (exitFailure, exitSuccess)
-
-import qualified Data.CollectionJSON.Tests
-
-main :: IO ()
-main =
-  do success <- and <$> sequence [ Data.CollectionJSON.Tests.runTests ]
-     if success then exitSuccess else exitFailure
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
