haskell-hedgehog-classes icon indicating copy to clipboard operation
haskell-hedgehog-classes copied to clipboard

Integration with hspec

Open chshersh opened this issue 3 years ago • 3 comments

It would be nice if hedgehog-classes could be integrated with other tasting frameworks, e.g. hspec.

Currently, when I run lawsCheck inside the hspec test suite, I see the following slightly messy output:

Tests
  Lecture 3
    Gold
      Laws: Semigroup [ ]Semigroup: Associativity    ✓ <interactive> passed 100 tests.
Semigroup: Concatenation    ✓ <interactive> passed 100 tests.
Semigroup: Times    ✓ <interactive> passed 100 tests.
      Laws: Semigroup [✔]
      Laws: Monoid [ ]Monoid: Left Identity    ✓ <interactive> passed 100 tests.
Monoid: Right Identity    ✓ <interactive> passed 100 tests.
Monoid: Associativity    ✓ <interactive> passed 100 tests.
Monoid: Concatenation    ✓ <interactive> passed 100 tests.
      Laws: Monoid [✔]

Here is my relevant test-suite code:

    describe "Gold" $ do
        it "Laws: Semigroup" $
            lawsCheck (semigroupLaws genGold) `shouldReturn` True
        it "Laws: Monoid" $
            lawsCheck (monoidLaws genGold) `shouldReturn` True

I see that hedgehog already has integration with hspec in the form of hspec-hedgehog package. The integration is done my providing relevant instances to the PropertyT data type.

So, I wonder, if hedgehog-classes can expose PropertyT of the underlying laws to be used with hspec 🤔 Or in any other way 🙂

chshersh avatar Jan 09 '22 14:01 chshersh

I would take a PR for this.

chessai avatar Dec 17 '22 04:12 chessai

This can be done with

import Test.Hspec
import Test.Hspec.Hedgehog
import Control.Monad
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import Hedgehog.Classes
import Hedgehog.Internal.Property

satisfies :: Gen a -> (Gen a -> Laws) -> Spec
satisfies gen laws = do
  describe (className <> " instance") $ do
    forM_ properties $ \ (name, p) -> do
      it ("satisfies " <> name) (propertyTest p)
  where
    Laws className properties = laws gen

which then can be used with e.g.:

main :: IO ()
main = hspec spec

arbitraryString :: Gen [Char]
arbitraryString = Gen.string (Range.constant 0 10) (Gen.unicodeAll)

spec :: Spec
spec = do
  describe "String" $ do
    arbitraryString `satisfies` eqLaws
    arbitraryString `satisfies` monoidLaws

(note that this ignores the propertyConfig and instead uses the settings provided by hspec)

sol avatar Nov 13 '23 10:11 sol

Also, for satisfies1, I think you can do the following:

-- needs AllowAmbiguousTypes, TypeApplications, ScopedTypeVariables, QuantifiedConstraints, RankNTypes

satisfies1 :: forall c f. (c f, forall x. Eq x => Eq (f x), forall x. Show x => Show (f x))
  => (forall a. Gen a -> Gen (f a))
  -> ((c f, forall x. Eq x => Eq (f x), forall x. Show x => Show (f x)) => (forall x. Gen x -> Gen (f x)) -> Laws)
  -> Spec
satisfies1 gen laws = case laws gen of
  Laws className properties -> do
    describe (className <> " instance") $ do
      forM_ properties $ \(name, p) -> do
        it ("satisfies " <> name) (propertyTest p)

genList :: Gen a -> Gen [a]
genList = Gen.list (Range.linear 0 6)

foo :: Spec ()
foo = satisfies1 @Applicative @[] genList applicativeLaws

and you could basically do the same thing for satisfies2.

chessai avatar Jan 21 '24 19:01 chessai