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 +9/−0
- LICENSE +30/−0
- README.md +104/−0
- Setup.hs +2/−0
- app/Main.hs +78/−0
- hspec-golden.cabal +73/−0
- src/Test/Hspec/Golden.hs +137/−0
- test/Spec.hs +1/−0
- test/Test/Hspec/GoldenSpec.hs +75/−0
+ 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+[](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