1

Lets say I have the following custom data type:

data Animal = Characteristics [Char] (Set.Set [Char])

and some function

checkAnimalType :: [Char] -> Animal -> [Animal]

now I'm trying to write hspec tests for this like so:

describe "checkAnimalType" $ do
      it "returns a list of animals" $ do
        (checkAnimalType ["foo", "coo", "doo", "bar", "moo"](Characteristics "foo" $ Set.fromList(["foo", "coo"]))) $ `shouldBe` [(Characteristics "foo" $ Set.fromList(["cockadoodledoo"]))]

this fails with:

No instance for (Eq Animal) arising from a use of ‘shouldBe’

My question is, is it possible to temporarily, within the scope of the tests, implement the Eq typeclass on Animal? Or is there a better way to do this?

7
  • You can do deriving Eq while declaring Animal or implement your own instances for Eq. Commented Jan 17, 2016 at 0:17
  • but is it possible to derive in the test without deriving in the actual source? Commented Jan 17, 2016 at 0:39
  • you can write your own instance declaration; should be uses the (==) operator internally which is provided by Eq thus you cannot test without it Commented Jan 17, 2016 at 0:42
  • You could create a module with only the tests, and define there a standalone instance, so that it does not get exported elsewhere. Commented Jan 17, 2016 at 0:43
  • 2
    Maybe you can define instances of Eq for your data type in your test module. But I would ask you to re-think your approach. If you feel your testing requires Eq instances and the code doesn't require it, then there is some problem with the design. Commented Jan 17, 2016 at 0:45

3 Answers 3

4

My question is, is it possible to temporarily, within the scope of the tests, implement the Eq typeclass on Animal?

Within the scope of the module, sure. But if that module gets imported, you're going to leak the instance into other modules. That's why creating instances is only recommended in the same module the data type has been defined, or where the class has been defined. Otherwise you end up with orphan instances.

Or is there a better way to do this?

Is it even remotely possible that the user want to compare characteristics? Then derive Eq. It's the cleanest way. Also, you're going to need a Show instance, so you're probably deriving something already:

data Animal = Characteristics [Char] (Set.Set [Char]) deriving (Show, Eq)

If you cannot change the original source you can still use -XStandaloneDeriving to derive the instance in another module (see orphan instances above, though).

However if you actually want to use some special Eq test you can either fiddle around with newtype wrappers, or simply write your own combinator:

-- newtype variant
newtype TAnimal = TAnimal Animal
instance Eq TAnimal where ...
instance Show TAnimal where...

animalShouldBe :: Animal -> Animal -> Expectation
animalShouldBe = shouldBe `on` TAnimal

-- custom operator variant
withShouldBe :: (Show a) => (a -> a -> Bool) -> a -> a -> Expectation
withShouldBe f a e = unless (f a e) $ expectationFailure msg
 where msg = "expected: " ++ show e ++ ", but got " ++ show a

 animalShouldBe = withShouldBe animalEqualityTest

-- Fun fact: shouldBe = withShouldBe (==)
Sign up to request clarification or add additional context in comments.

Comments

2

It's a bit of a strange requirement. But you can just add the Eq instance inside your test. Haskell doesn't have a restriction on having the instance in the same source file or module as the data type or type class.

If you wanted to derive the instance, rather than writing it yourself, you can (in GHC) use the extension StandaloneDeriving and write:

deriving instance Eq Animal

Edit: Having said that, I can't see a good reason why you wouldn't just add the instance along with your main definition of Animal. It won't do any harm, and it's pretty standard to add common typeclass derivations up front, just in case you need them later.

Comments

2

You should test observable behaviour, not implementation details. If, for whatever reason, whether two Animals are equal is not something that should be observable (and hence you don't want to implement Eq in your source code), then don't use that property in your test.

So instead of testing that checkAnimalType returns a particular list of animals, focus on what you're supposed to be able to do with that list, and check that those properties hold.

E.g. If you can get names out of them, check those. If it's supposed to be returning all the Animals that have some particular relationship to the input Animal, check that that relationship holds (and possibly that it doesn't hold for any other Animals). If Animal is not Eq, but you can canonically convert it to something that is (say if Animal holds some internal implementation gunk that's not Eq friendly, but you can serialize them or otherwise convert them to a more "just data" format), convert the output of checkAnimalType and then use the Eq of that.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.