packages feed

mockcat-1.3.0.0: test/Test/MockCat/MockSpec.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wno-type-defaults #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ScopedTypeVariables #-}
{- HLINT ignore "Use newtype instead of data" -}

module Test.MockCat.MockSpec (spec) where

import qualified Control.Exception as E
import Test.Hspec
import Test.MockCat
import Prelude hiding (any)
import Data.List (isInfixOf)

spec :: Spec
spec = do
  describe "Test of Mock" do
    describe "combination test" do
      it "arity = 1" do
        f <- mock $ True ~> False
        f True `shouldBe` False

      it "arity = 2" do
        f <- mock $ True ~> False ~> True
        f True False `shouldBe` True

      it "arity = 3" do
        f <- mock $ True ~> "False" ~> True ~> "False"
        f True "False" True `shouldBe` "False"

      it "arity = 4" do
        f <- mock $ True ~> "False" ~> True ~> "False" ~> True
        f True "False" True "False" `shouldBe` True

      it "Param ~> a" do
        f <- mock $ any ~> False
        f True `shouldBe` False

      it "Param ~> (a ~> b)" do
        f <- mock $ any ~> False ~> True
        f True False `shouldBe` True

      it "a     ~> (Param ~> b)" do
        f <- mock $ True ~> any ~> True
        f True False `shouldBe` True

      it "Param ~> (Param ~> a)" do
        f <- mock $ any ~> any ~> True
        f True False `shouldBe` True

      it "a     ~> (Param ~> (Param ~> a))" do
        f <- mock $ "any" ~> any ~> any ~> True
        f "any" "any" "any" `shouldBe` True

      it "param ~> (Param ~> (Param ~> a))" do
        f <- mock $ any ~> any ~> any ~> True
        f "any" "any" "any" `shouldBe` True

    describe "Non-Eq/Show support" do
      it "can mock function with NoEq argument using any" do
        f <- mock $ any @NoEq ~> "result"
        f (NoEq "val") `shouldBe` "result"
      
      it "can mock function with NoEq argument using expect" do
        f <- mock $ expect_ (const True) ~> "result"
        f (NoEq "val") `shouldBe` "result"

      it "can stub function with NoEq argument" do
        let f = stub $ any @NoEq ~> "result"
        f (NoEq "val") `shouldBe` "result"
    
      it "can mock function with NoEq argument matching its value" do
        -- Even without an Eq instance, you can match based on field values using 'expect'.
        f <- mock $ expect (\(NoEq val) -> val == "target") "NoEq with 'target'" ~> "hit"
        f (NoEq "target") `shouldBe` "hit"
        evaluate (f (NoEq "other")) `shouldThrow` anyErrorCall

      it "can mock function taking a function" do
        f <- mock $ expect_ (\(g :: Int -> Int) -> g (5 :: Int) == (25 :: Int)) ~> "result"
        let g x = x * x
        f g `shouldBe` "result"

  describe "Monad" do
    it "Return IO Monad." do
      f <- mock $ "Article Id" ~> pure @IO "Article Title"

      result <- f "Article Id"

      result `shouldBe` "Article Title"

  describe "Appropriate message when a test fails." do
    describe "anonymous mock" do
      describe "apply" do
        it "simple mock" do
          f <- mock $ "a" ~> pure @IO True
          f "b"
            `shouldThrow` errorContains "expected: \"a\"\n   but got: \"b\"\n             ^^"

        it "multi mock" do
          f <-
            mock $ do
              onCase $ "aaa" ~> (100 :: Int) ~> pure @IO True
              onCase $ "bbb" ~> (200 :: Int) ~> pure @IO False

          f "aaa" 200
            `shouldThrow` errorContains "expected one of the following:"

    describe "named mock" do
      describe "aply" do
        it "simple mock" do
          f <- mock (label "mock function") $ "a" ~> pure @IO ()
          f "b" `shouldThrow` errorContains "expected: \"a\"\n   but got: \"b\"\n             ^^"

        it "multi mock" do
          f <-
            mock (label "mock function")
              do 
                onCase $ "aaa" ~> True ~> pure @IO True
                onCase $ "bbb" ~> False ~> pure @IO False
          evaluate (f "aaa" False) `shouldThrow` errorContains "expected one of the following:"

  describe "use expectation" do
    it "expectByExpr" do
      f <- mock $ $(expectByExpr [|\x -> x == "y" || x == "z"|]) ~> True
      f "y" `shouldBe` True

  describe "repeatable" do
    it "arity = 1" do
      f <- mock $ do
        onCase $ "a" ~> True
        onCase $ "b" ~> False
        onCase $ "a" ~> False
        onCase $ "b" ~> True
        
      v1 <- evaluate $ f "a"
      v2 <- evaluate $ f "a"
      v3 <- evaluate $ f "b"
      v4 <- evaluate $ f "b"
      v1 `shouldBe` True
      v2 `shouldBe` False
      v3 `shouldBe` False
      v4 `shouldBe` True

    it "arity = 2" do
      f <- mock $ do
        onCase $ "a" ~> "b" ~> (0 :: Int)
        onCase $ "a" ~> "c" ~> (1 :: Int)
        onCase $ "a" ~> "b" ~> (2 :: Int)
        onCase $ "a" ~> "c" ~> (3 :: Int)

      v1 <- evaluate $ f "a" "b"
      v2 <- evaluate $ f "a" "b"
      v3 <- evaluate $ f "a" "c"
      v4 <- evaluate $ f "a" "c"
      v5 <- evaluate $ f "a" "b"
      v1 `shouldBe` (0 :: Int)
      v2 `shouldBe` (2 :: Int)
      v3 `shouldBe` (1 :: Int)
      v4 `shouldBe` (3 :: Int)
      v5 `shouldBe` (2 :: Int)

  describe "constant" do
    it "mock" do
      f <- mock "foo"
      f `shouldBe` "foo"
    
    it "createNamedMockFn" do
      f <- mock (label "const") "foo"
      f `shouldBe` "foo"

    it "verify constant IO mock" do
      f <- mock $ pure @IO "foo"
      f `shouldReturn` "foo"
      f `shouldReturn` "foo"
      f `shouldReturn` "foo"

    it "verify constant multi IO mock" do
      f <- mock $ do
        onCase $ pure @IO "foo"
        onCase $ pure @IO "bar"
        onCase $ pure @IO "baz"

      f `shouldReturn` "foo"
      f `shouldReturn` "bar"
      f `shouldReturn` "baz"

class Eval a where
  evaluate :: a -> IO a

instance Eval [a] where
  evaluate v = do
    mapM_ (\x -> E.evaluate (x `seq` ())) v
    pure v

instance {-# OVERLAPPABLE #-} Eval a where
  evaluate = E.evaluate

errorContains :: String -> Selector E.ErrorCall
errorContains sub (E.ErrorCall msg) = sub `isInfixOf` msg

data NoEq = NoEq String deriving (Show)