[RFC] hspec-quickcheck-classes: testing typeclass laws from Hspec

I’ve created a small library called hspec-quickcheck-classes, for testing typeclass laws within Hspec specifications.

It’s a tiny library (with just one function), but I’d be very grateful for any feedback before I release it on Hackage.

What problem does it solve?

This library integrates Hspec with quickcheck-classes. For those not familiar:

  • Hspec is a very popular Haskell testing framework that provides a readable DSL for structuring tests.

  • quickcheck-classes is a library that provides QuickCheck property tests for the laws of typeclasses such as Eq, Ord, Functor, Applicative, Monad, and many others. It defines a Laws data type, which bundles together a list of named properties for a single typeclass.

Using quickcheck-classes within an Hspec specification requires a small but repetitive amount of boilerplate.

This library handles this boilerplate for you, constructing a Spec from a given type and a set of Laws. The generated Spec consists of a tree with an outer node for the type, an inner node for each typeclass, and a leaf for each law being tested (where each law has a corresponding QuickCheck property):

<type>                 ← outer node (type)
├── <typeclass #a>     ← inner node (typeclass)
│   ├── <law #1>       ← leaf (law)
│   ├── <law #2>
│   └── ...
├── <typeclass #b>
│   ├── <law #1>
│   ├── <law #2>
│   └── ...
└── <typeclass #c>
    ├── <law #1>
    ├── <law #2>
    └── ...
...

The API

The library exposes just a single function:

testLaws
  :: forall a. (Typeable a, HasCallStack) 
  => [Proxy a -> Laws]
  -> Spec

Usage looks like this:

import Test.Hspec
  ( hspec )
import Test.Hspec.QuickCheck.Classes
  ( testLaws )
import Test.QuickCheck
  ( Arbitrary (..) )
import Test.QuickCheck.Classes
  ( eqLaws, ordLaws, showLaws )

-- Import the data type you'd like to test:
import Data.Foo
  ( Foo (..) )

-- Define (or import) an 'Arbitrary' instance for your data type:
instance Arbitrary Foo where
  arbitrary = ...
  shrink = ...

main :: IO ()
main = hspec $ do

  -- Test that your data type obeys the laws of 'Eq', 'Ord', and 'Show':
  testLaws @Foo
    [ eqLaws
    , ordLaws
    , showLaws
    ]

This produces output along the lines of:

Testing laws for Foo
  Eq
    Transitive [✔]
      +++ OK, passed 100 tests.
    Symmetric [✔]
      +++ OK, passed 100 tests.
    Reflexive [✔]
      +++ OK, passed 100 tests.
  Ord
    Antisymmetry [✔]
      +++ OK, passed 100 tests.
    Transitivity [✔]
      +++ OK, passed 100 tests.
    Totality [✔]
      +++ OK, passed 100 tests.
  Show
    Show [✔]
      +++ OK, passed 100 tests.
    Equivariance: showsPrec [✔]
      +++ OK, passed 100 tests.
    Equivariance: showList [✔]
      +++ OK, passed 100 tests.

On failure, the output includes a counterexample, and the location of the test that failed in the test source code.

For example, here’s a failing test taken from the monoidmap package, showing a violation of the LeftReductive laws (due to a deliberately-introduced bug):

test/Data/MonoidMap/Internal/ClassSpec.hs:92:9: 
1) Data.MonoidMap, Testing laws for MonoidMap String String, LeftReductive, leftReductiveLaw_stripPrefix
   Falsified (after 20 tests and 10 shrinks):
   Property not satisfied:
     maybe b (a <>) (stripPrefix a b) == b
   Values:
     a =
       fromList []
     b =
       fromList [("A","a")]
     stripPrefix a b =
       Just (fromList [("A","a")])
     maybe b (a <>) (stripPrefix a b) =
       fromList []

Kind polymorphism

To support testing of laws for higher-kinded classes like Functor, Applicative, and Traversable, the testLaws function is kind-polymorphic, with no special casing required.

For example, with Maybe, which has kind Type -> Type:

testLaws @Maybe
  [ applicativeLaws
  , functorLaws
  , monadLaws
  , foldableLaws
  , traversableLaws
  ]

And Either, which has kind Type -> Type -> Type:

testLaws @Either
  [ bifoldableLaws
  , bifunctorLaws
  , bitraversableLaws
  ]

Feedback

Obviously this is a tiny library, but if anyone has any feedback they’d like to share about the API, the documentation, or the implementation, I’d be very grateful to hear your thoughts before the library is published.

Many thanks for reading!

10 Likes

Just a quick update: hspec-quickcheck-classes is now available on Hackage:

Many thanks to those who read through the RFC!