packages feed

hspec-golden (empty) → 0.1.0.0

raw patch · 9 files changed

+509/−0 lines, 9 filesdep +basedep +directorydep +hspecsetup-changed

Dependencies added: base, directory, hspec, hspec-core, hspec-golden, silently, simple-get-opt

Files

+ ChangeLog.md view
@@ -0,0 +1,9 @@+# Changelog for hspec-golden++## 0.1.0.0+#### Add+* CLI to update `actual` files to `golden`+* Basic functionality to have Golden tests in `hspec`.+* `Golden str` type to write your own golden tests for the kind of+type that you need and a variable output folder.+* `defaultGolden` helper to create golden tests when your results are simple `String`s.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Stack Builders (c) 2019++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Author name here nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,104 @@+# hspec-golden+[![Build Status](https://travis-ci.org/stackbuilders/hspec-golden.svg?branch=master)](https://travis-ci.org/stackbuilders/hspec-golden)++## Description+Golden tests store the expected output in a separated file. Each time a golden test+is executed the output of the subject under test (SUT) is compared with the+expected output. If the output of the SUT changes then the test will fail until+the expected output is updated.++`hspec-golden` allows you to write golden tests using the popular `hspec`.++## Getting started++You can write golden tests using `defaultGolden` helper:++```haskell+describe "myFunc" $+  it "generates the right output with the right params" $+     let output = show $ myFunc params+       in defaultGolden "myFunc" output+```++The first parameter of `defaultGolden` is the golden file name. I recommend you to use+`show` and `'functionName` (enable `TemplateHaskellQuotes` for the quote) to+always have a unique name for your file. Example: `show 'myFunc == MyModule.myFunc`.+Although, you can name it as you like.++In case your output isn't a `String` you can define your own `Golden` test+using the `Golden` data type:++```haskell+{-# LANGUAGE TemplateHaskellQuotes #-}++import           Test.Hspec+import           Test.Hspec.Golden+import           Data.Text (Text)+import qualified Data.Text.IO as T++myGoldenTest :: String -> Text -> Golden Text+myGoldenTest name actualOutput =+  Golden {+    output = actualOutput,+    encodePretty = prettyText,+    writeToFile = T.writeFile,+    readFromFile = T.readFile,+    testName = name,+    directory = ".myGoldenTestDir"+  }++describe "myTextFunc" $+  it "generates the right output with the right params" $+     let textOutput = myTextFunc params+       in myGoldenTest (show 'myTextFunc) textOutput+```++## Installing CLI++You can install the `hspec-golden` command line interface (CLI) with `stack`:++```+$ stack install hspec-golden+```++or `cabal`:+++```+$ cabal install hspec-golden+```++The CLI is called `hgold`:++```+$ hgold++Parameters:+  DIR    The testing directory where you're dumping your results (default: .golden/)++Flags:+  -u[DIR]  --update[=DIR]  Replaces `golden` files with `actual` files+  -v       --version       Displays the version of hgold+  -h       --help          Displays help information+```++Update the golden tests under `.golden` directory:++```+$ hgold -u+```++Update the golden tests under `.myGoldenTest` directory:++```+$ hgold -u .myGoldenTest+```++## Licensing++MIT, see the [LICENSE file](./LICENSE).++## Contributing+Pull requests for modifications to this program are welcome! Fork and open a PR.++If you're looking for a place to start, you may want to check the [open issue](https://github.com/stackbuilders/hspec-golden/issues).
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE RecordWildCards #-}++module Main where++import           Control.Monad (forM_, when)+import           Data.Version (showVersion)+import           Paths_hspec_golden (version)+import           SimpleGetOpt+import           System.Directory (doesFileExist, listDirectory, renameFile)+import qualified Test.Hspec.Golden as G++-- CLI Parameters+data Params =+  Params+    { version_     :: Bool+    , showHelp     :: Bool+    , updateDir    :: FilePath+    , shouldUpdate :: Bool+    } deriving Show++defaultDirGoldenTest :: FilePath+defaultDirGoldenTest = G.directory (G.defaultGolden "" "")++opts :: OptSpec Params+opts =+  OptSpec+    { progDefaults = Params False True defaultDirGoldenTest False+    , progOptions =+      [ Option ['u'] ["update"] "Replaces `golden` files with `actual` files"+        $ OptArg "DIR" $ \maybeDir ->+          case maybeDir of+            Nothing -> \s ->+              Right s+                { shouldUpdate = True+                , updateDir = defaultDirGoldenTest+                , showHelp = False+                }+            Just dir -> \s ->+              Right s+                { shouldUpdate = True+                , updateDir = dir+                , showHelp = False+                }+      , Option ['v'] ["version"] "Displays the version of hgold"+        $ NoArg $ \s -> Right s { version_ = True, showHelp = False }+      , Option ['h'] ["help"] "Displays help information"+        $ NoArg $ \s -> Right s+      ]+    , progParamDocs = [("DIR", "The testing directory where you're dumping your results (default: .golden/)")]+    , progParams = const Right+    }++-- Update files+updateGolden :: FilePath -> IO ()+updateGolden dir = do+  putStrLn "Replacing golden with actual..."+  goldenTests <- listDirectory dir+  forM_ goldenTests (mvActualToGolden dir)+  putStrLn "Finish..."++mvActualToGolden :: FilePath -> FilePath -> IO ()+mvActualToGolden goldenDir testName =+  let actualFilePath = goldenDir ++ "/" ++ testName ++ "/" ++ "actual"+      goldenFilePath = goldenDir ++ "/" ++ testName ++ "/" ++ "golden"+   in do+     actualFileExist <- doesFileExist actualFilePath+     when actualFileExist (do+       putStrLn $ "  Replacing file: " ++ goldenFilePath ++ " with: " ++ actualFilePath+       renameFile actualFilePath goldenFilePath)++-- MAIN++main :: IO ()+main = do+  Params {..} <- getOpts opts+  when showHelp (dumpUsage opts)+  when version_ (putStrLn $ showVersion version)+  when shouldUpdate (updateGolden updateDir)
+ hspec-golden.cabal view
@@ -0,0 +1,73 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.30.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 83cc81bf5ff29892ea3a331b29c4837fba7d68c839fa614144362ac7699e4d04++name:           hspec-golden+version:        0.1.0.0+synopsis:       Golden tests for hspec+description:    Please see the README on GitHub at <https://github.com/stackbuilders/hspec-golden#README>+category:       Testing+homepage:       https://github.com/stackbuilders/hspec-golden#readme+bug-reports:    https://github.com/stackbuilders/hspec-golden/issues+author:         Stack Builders+maintainer:     cmotoche@stackbuilders.com+copyright:      2019 Stack Builders Inc+license:        MIT+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://github.com/stackbuilders/hspec-golden++library+  exposed-modules:+      Test.Hspec.Golden+  other-modules:+      Paths_hspec_golden+  hs-source-dirs:+      src+  build-depends:+      base >=4.6 && <5+    , directory+    , hspec-core+  default-language: Haskell2010++executable hgold+  main-is: Main.hs+  other-modules:+      Paths_hspec_golden+  hs-source-dirs:+      app+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.6 && <5+    , directory >=1.2.5.0+    , hspec-golden+    , simple-get-opt+  default-language: Haskell2010++test-suite hspec-golden-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Test.Hspec.GoldenSpec+      Paths_hspec_golden+  hs-source-dirs:+      test+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.6 && <5+    , directory+    , hspec+    , hspec-core+    , hspec-golden+    , silently+  default-language: Haskell2010
+ src/Test/Hspec/Golden.hs view
@@ -0,0 +1,137 @@+{-|+Module      : Test.Hspec.Golden+Description : Golden tests for Hspec+Copyright   : Stack Builders (c), 2019+License     : MIT+Maintainer  : cmotoche@stackbuilders.com+Stability   : experimental+Portability : portable++Golden tests store the expected output in a separated file. Each time a golden test+is executed the output of the subject under test (SUT) is compared with the+expected output. If the output of the SUT changes then the test will fail until+the expected output is updated. We expose 'defaultGolden' for output of+type @String@. If your SUT has a different output, you can use 'Golden'.+-}++{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE RecordWildCards   #-}+{-# LANGUAGE TypeFamilies      #-}++module Test.Hspec.Golden+  ( Golden(..)+  , defaultGolden+  )+  where++import           Data.IORef+import           System.Directory     (createDirectoryIfMissing, doesFileExist)+import           Test.Hspec.Core.Spec (Example (..), FailureReason (..),+                                       Result (..), ResultStatus (..))+++-- | Golden tests parameters+--+-- @+-- import           Data.Text (Text)+-- import qualified Data.Text.IO as T+--+-- goldenText :: String -> Text -> Golden Text+-- goldenText name actualOutput =+--   Golden {+--     output = actualOutput,+--     encodePretty = prettyText,+--     writeToFile = T.writeFile,+--     readFromFile = T.readFile,+--     testName = name,+--     directory = ".specific-golden-dir"+--   }+--+-- describe "myTextFunc" $+--   it "generates the right output with the right params" $+--     goldenText "myTextFunc" (myTextFunc params)+-- @++data Golden str =+  Golden {+    output       :: str, -- ^ Output+    encodePretty :: str -> String, -- ^ Makes the comparison pretty when the test fails+    writeToFile  :: FilePath -> str -> IO (), -- ^ How to write into the golden file the file+    readFromFile :: FilePath -> IO str, -- ^ How to read the file,+    testName     :: String, -- ^ Test name (make sure it's unique otherwise it could be override)+    directory    :: FilePath -- ^ Directory where you write your tests+  }++instance Eq str => Example (Golden str) where+  type Arg (Golden str) = ()+  evaluateExample e = evaluateExample (\() -> e)++instance Eq str => Example (arg -> Golden str) where+  type Arg (arg -> Golden str) = arg+  evaluateExample golden _ action _ = do+    ref <- newIORef (Result "" Success)+    action $ \arg -> do+      r <- runGolden (golden arg)+      writeIORef ref (fromGoldenResult r)+    readIORef ref++-- | Transform a GoldenResult into a Result from Hspec++fromGoldenResult :: GoldenResult -> Result+fromGoldenResult FirstExecution  = Result "First time execution. Golden file created." Success+fromGoldenResult SameOutput      = Result "Golden and Actual output hasn't changed" Success+fromGoldenResult (MissmatchOutput expected actual) =+  Result "Files golden and actual not match"+         (Failure Nothing (ExpectedButGot Nothing expected actual))++-- | An example of Golden tests which output is 'String'+--+-- @+--  describe "html" $ do+--    context "given a valid generated html" $+--      it "generates html" $+--        defaultGolden "html" someHtml+-- @++defaultGolden :: String -> String -> Golden String+defaultGolden name output_ =+  Golden {+    output = output_,+    encodePretty = show,+    testName = name,+    writeToFile = writeFile,+    readFromFile = readFile,+    directory = ".golden"+  }++-- | Possible results from a golden test execution++data GoldenResult =+   MissmatchOutput String String+   | SameOutput+   | FirstExecution++-- | Runs a Golden test.++runGolden :: Eq str => Golden str -> IO GoldenResult+runGolden Golden{..} =+  let goldenTestDir = directory ++ "/" ++ testName+      goldenFilePath = goldenTestDir ++ "/" ++ "golden"+      actualFilePath = goldenTestDir ++ "/" ++ "actual"+   in do+     createDirectoryIfMissing True goldenTestDir+     goldenFileExist <- doesFileExist goldenFilePath++     -- the actual file is always written, this way, hgold will always+     -- upgrade based on the latest run+     writeToFile actualFilePath output++     if not goldenFileExist+       then writeToFile goldenFilePath output+            >> return FirstExecution+       else do+          contentGolden <- readFromFile goldenFilePath++          if contentGolden == output+             then return SameOutput+             else return $ MissmatchOutput (encodePretty contentGolden) (encodePretty output)
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/Test/Hspec/GoldenSpec.hs view
@@ -0,0 +1,75 @@+module Test.Hspec.GoldenSpec (spec) where++import           Control.Monad          (void)++import           Test.Hspec+import qualified Test.Hspec.Core.Runner as H+import qualified Test.Hspec.Core.Spec   as H+import           Test.Hspec.Golden++import           System.Directory+import           System.IO.Silently++{-# ANN module "HLint: ignore Reduce duplication" #-}++fixtureContent, fixtureTestName, fixtureUpdatedContent :: String+fixtureUpdatedContent = "different text"+fixtureContent = "simple text"+fixtureTestName = "id"++goldenTestDir, goldenFile, actualFile :: FilePath+goldenTestDir = directory (defaultGolden "" "") ++ "/" ++ "id"+goldenFile = goldenTestDir ++ "/" ++ "golden"+actualFile = goldenTestDir ++ "/" ++ "actual"++fixtureTest :: String -> H.Spec+fixtureTest content =+  H.describe "id" $+    H.it "should work" $+      defaultGolden fixtureTestName content++removeFixtures :: IO ()+removeFixtures = removeDirectoryRecursive goldenTestDir++runSpec :: H.Spec -> IO [String]+runSpec = captureLines . H.hspecResult++captureLines :: IO a -> IO [String]+captureLines = fmap lines . capture_++spec :: Spec+spec =+  describe "Golden" $ after_ removeFixtures $ do+    context "when the test is executed for the first time" $ do+      it "should create a `golden` file" $ do+         void $ runSpec $ fixtureTest fixtureContent+         goldenFileContent <- readFile goldenFile+         goldenFileContent `shouldBe` fixtureContent++      it "should create a `actual` file" $ do+         void $ runSpec $ fixtureTest fixtureContent+         actualFileContent <- readFile goldenFile+         actualFileContent `shouldBe` fixtureContent++    context "when the output is updated" $+      context "when the test is executed a second time" $ do+        it "should create the `actual` output file" $ do+           void $ runSpec $ fixtureTest fixtureContent+           void $ runSpec $ fixtureTest fixtureUpdatedContent+           actualFileContent <- readFile actualFile+           actualFileContent `shouldBe` fixtureUpdatedContent++        it "shouldn't overide the `golden` file" $ do+           void $ runSpec $ fixtureTest fixtureContent+           void $ runSpec $ fixtureTest fixtureUpdatedContent+           goldenFileContent <- readFile goldenFile+           goldenFileContent `shouldBe` fixtureContent+++    context "when the output is not updated" $+      context "when the test is executed a second time" $+        it "shouldn't change the `golden` file content" $ do+           void $ runSpec $ fixtureTest fixtureContent+           void $ runSpec $ fixtureTest fixtureContent+           goldenFileContent <- readFile goldenFile+           goldenFileContent `shouldBe` fixtureContent