diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# 0.3.1
+
+* Add golden testing using tasty
+* Fix grep render [#11](https://github.com/psibi/tldr-hs/issues/11)
+* Fixes [#2](https://github.com/psibi/tldr-hs/issues/2)
+
 # 0.3.0
 
 * Add default completion support from optparse-applicative
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -11,6 +11,7 @@
 import System.FilePath
 import System.Process.Typed
 import System.Environment (getArgs, withArgs)
+import GHC.IO.Handle.FD (stdout)
 import Paths_tldr (version)
 import Data.Version (showVersion)
 
@@ -106,5 +107,5 @@
          initializeTldrPages
          let page = pageName opts
          fname <- getPagePath page
-         maybe (putStrLn ("No tldr entry for " <> page)) renderPage fname
+         maybe (putStrLn ("No tldr entry for " <> page)) (flip renderPage stdout) fname 
     compOpts@(CompletionInvoked _) -> handleParseResult compOpts >> return ()
diff --git a/src/Tldr.hs b/src/Tldr.hs
--- a/src/Tldr.hs
+++ b/src/Tldr.hs
@@ -12,10 +12,12 @@
   ) where
 
 import Data.Text
+import qualified Data.Text as T
 import qualified Data.Text.IO as TIO
 import CMark
 import System.Console.ANSI
 import Data.Monoid ((<>))
+import GHC.IO.Handle (Handle)
 
 data ConsoleSetting = ConsoleSetting
   { italic :: Bool
@@ -54,15 +56,15 @@
   , SetColor Foreground (fgIntensity cons) (fgColor cons)
   ]
 
-renderNode :: NodeType -> IO ()
-renderNode (TEXT txt) = TIO.putStrLn txt
-renderNode (HTML_BLOCK txt) = TIO.putStrLn txt
-renderNode (CODE_BLOCK _ txt) = TIO.putStrLn txt
-renderNode (HTML_INLINE txt) = TIO.putStrLn txt
-renderNode (CODE txt) = TIO.putStrLn ("   " <> txt)
-renderNode LINEBREAK = TIO.putStrLn ""
-renderNode (LIST _) = TIO.putStrLn "" >> TIO.putStr " - "
-renderNode _ = return ()
+renderNode :: NodeType -> Handle -> IO ()
+renderNode (TEXT txt) handle         = TIO.hPutStrLn handle txt
+renderNode (HTML_BLOCK txt) handle   = TIO.hPutStrLn handle txt
+renderNode (CODE_BLOCK _ txt) handle = TIO.hPutStrLn handle txt
+renderNode (HTML_INLINE txt) handle  = TIO.hPutStrLn handle txt
+renderNode (CODE txt) handle         = TIO.hPutStrLn handle ("   " <> txt)
+renderNode LINEBREAK handle          = TIO.hPutStrLn handle ""
+renderNode (LIST _) handle           = TIO.hPutStrLn handle "" >> TIO.hPutStr handle " - "
+renderNode _ _                       = return ()
 
 changeConsoleSetting :: NodeType -> IO ()
 changeConsoleSetting (HEADING _) = setSGR $ toSGR headingSetting
@@ -81,11 +83,29 @@
   }
 changeConsoleSetting _ = return ()
 
-handleNode :: Node -> IO ()
-handleNode (Node _ ntype xs) = do
+handleSubsetNodeType :: NodeType -> Text
+handleSubsetNodeType (HTML_BLOCK txt) = txt
+handleSubsetNodeType (CODE_BLOCK _ txt) = txt
+handleSubsetNodeType (TEXT txt) = txt
+handleSubsetNodeType (HTML_INLINE txt) = txt
+handleSubsetNodeType (CODE txt) = txt
+handleSubsetNodeType _ = mempty
+
+
+handleSubsetNode :: Node -> Text
+handleSubsetNode (Node _ ntype xs) = handleSubsetNodeType ntype <> (T.concat $ Prelude.map handleSubsetNode xs)
+
+handleParagraph :: [Node] -> Handle -> IO ()
+handleParagraph xs handle = TIO.hPutStrLn handle $ T.concat $ Prelude.map handleSubsetNode xs
+
+
+handleNode :: Node -> Handle -> IO ()
+handleNode (Node _ PARAGRAPH xs) handle = handleParagraph xs handle
+handleNode (Node _ ITEM xs) handle = handleParagraph xs handle
+handleNode (Node _ ntype xs) handle = do
   changeConsoleSetting ntype
-  renderNode ntype
-  mapM_ (\(Node _ ntype' ns) -> renderNode ntype' >> mapM_ handleNode ns) xs
+  renderNode ntype handle
+  mapM_ (\(Node _ ntype' ns) -> renderNode ntype' handle >> mapM_ (flip handleNode $ handle) ns) xs
   setSGR [Reset]
 
 parsePage :: FilePath -> IO Node
@@ -94,7 +114,7 @@
   let node = commonmarkToNode [] page
   return node
 
-renderPage :: FilePath -> IO ()
-renderPage fname = do
+renderPage :: FilePath -> Handle -> IO ()
+renderPage fname handle = do
   node <- parsePage fname
-  handleNode node
+  handleNode node handle
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,2 +1,38 @@
+import Tldr
+import Test.Tasty
+import Test.Tasty.Golden (goldenVsFile)
+import System.IO (withBinaryFile, IOMode(..))
+import Data.Monoid ((<>))
+
+tests :: TestTree
+tests = testGroup "tldr Tests" [goldenTests]
+
+goldenTests :: TestTree
+goldenTests = testGroup "Golden tests" [gtests]
+
+renderPageToFile :: FilePath -> FilePath -> IO ()
+renderPageToFile mdfile opfile = do
+  withBinaryFile opfile WriteMode (\handle -> renderPage mdfile handle)
+
+-- For adding new command, you need to add:
+-- A new ".md" file for that command
+-- A new ".golden" file for the expected output
+
+commandTest :: String -> TestTree
+commandTest str = goldenVsFile (str <> " test") (golden str) (output str) (renderPageToFile (md str) (output str))
+    where
+      prefix = "test/data/"
+      golden cmd = prefix <> cmd <> ".golden"
+      output cmd = prefix <> cmd <> ".output"
+      md cmd = prefix <> cmd <> ".md"
+
+gtests :: TestTree
+gtests = testGroup "(render test)" 
+         [
+          commandTest "ls"
+         , commandTest "ps"
+         , commandTest "grep"
+         ]
+
 main :: IO ()
-main = putStrLn "Test suite not yet implemented"
+main = defaultMain tests
diff --git a/test/data/grep.golden b/test/data/grep.golden
new file mode 100644
--- /dev/null
+++ b/test/data/grep.golden
@@ -0,0 +1,26 @@
+grep
+Matches patterns in input text.Supports simple patterns and regular expressions.
+
+ - Search for an exact string:
+   grep {{search_string}} {{path/to/file}}
+
+ - Search in case-insensitive mode:
+   grep -i {{search_string}} {{path/to/file}}
+
+ - Search recursively (ignoring non-text files) in current directory for an exact string:
+   grep -RI {{search_string}} .
+
+ - Use extended regular expressions (supporting ?, +, {}, () and |):
+   grep -E {{^regex$}} {{path/to/file}}
+
+ - Print 3 lines of [C]ontext around, [B]efore, or [A]fter each match:
+   grep -{{C|B|A}} 3 {{search_string}} {{path/to/file}}
+
+ - Print file name with the corresponding line number for each match:
+   grep -Hn {{search_string}} {{path/to/file}}
+
+ - Use the standard input instead of a file:
+   cat {{path/to/file}} | grep {{search_string}}
+
+ - Invert match for excluding specific strings:
+   grep -v {{search_string}}
diff --git a/test/data/grep.md b/test/data/grep.md
new file mode 100644
--- /dev/null
+++ b/test/data/grep.md
@@ -0,0 +1,36 @@
+# grep
+
+> Matches patterns in input text.
+> Supports simple patterns and regular expressions.
+
+- Search for an exact string:
+
+`grep {{search_string}} {{path/to/file}}`
+
+- Search in case-insensitive mode:
+
+`grep -i {{search_string}} {{path/to/file}}`
+
+- Search recursively (ignoring non-text files) in current directory for an exact string:
+
+`grep -RI {{search_string}} .`
+
+- Use extended regular expressions (supporting `?`, `+`, `{}`, `()` and `|`):
+
+`grep -E {{^regex$}} {{path/to/file}}`
+
+- Print 3 lines of [C]ontext around, [B]efore, or [A]fter each match:
+
+`grep -{{C|B|A}} 3 {{search_string}} {{path/to/file}}`
+
+- Print file name with the corresponding line number for each match:
+
+`grep -Hn {{search_string}} {{path/to/file}}`
+
+- Use the standard input instead of a file:
+
+`cat {{path/to/file}} | grep {{search_string}}`
+
+- Invert match for excluding specific strings:
+
+`grep -v {{search_string}}`
diff --git a/test/data/ls.golden b/test/data/ls.golden
new file mode 100644
--- /dev/null
+++ b/test/data/ls.golden
@@ -0,0 +1,20 @@
+ls
+List directory contents.
+
+ - List files one per line:
+   ls -1
+
+ - List all files, including hidden files:
+   ls -a
+
+ - Long format list (permissions, ownership, size and modification date) of all files:
+   ls -la
+
+ - Long format list with size displayed using human readable units (KB, MB, GB):
+   ls -lh
+
+ - Long format list sorted by size (descending):
+   ls -lS
+
+ - Long format list of all files, sorted by modification date (oldest first):
+   ls -ltr
diff --git a/test/data/ls.md b/test/data/ls.md
new file mode 100644
--- /dev/null
+++ b/test/data/ls.md
@@ -0,0 +1,27 @@
+# ls
+
+> List directory contents.
+
+- List files one per line:
+
+`ls -1`
+
+- List all files, including hidden files:
+
+`ls -a`
+
+- Long format list (permissions, ownership, size and modification date) of all files:
+
+`ls -la`
+
+- Long format list with size displayed using human readable units (KB, MB, GB):
+
+`ls -lh`
+
+- Long format list sorted by size (descending):
+
+`ls -lS`
+
+- Long format list of all files, sorted by modification date (oldest first):
+
+`ls -ltr`
diff --git a/test/data/ps.golden b/test/data/ps.golden
new file mode 100644
--- /dev/null
+++ b/test/data/ps.golden
@@ -0,0 +1,20 @@
+ps
+Information about running processes.
+
+ - List all running processes:
+   ps aux
+
+ - List all running processes including the full command string:
+   ps auxww
+
+ - Search for a process that matches a string:
+   ps aux | grep {{string}}
+
+ - List all processes of the current user in extra full format:
+   ps --user $(id -u) -F
+
+ - List all processes of the current user as a tree:
+   ps --user $(id -u) f
+
+ - Get the parent pid of a process:
+   ps -o ppid= -p {{pid}}
diff --git a/test/data/ps.md b/test/data/ps.md
new file mode 100644
--- /dev/null
+++ b/test/data/ps.md
@@ -0,0 +1,27 @@
+# ps
+
+> Information about running processes.
+
+- List all running processes:
+
+`ps aux`
+
+- List all running processes including the full command string:
+
+`ps auxww`
+
+- Search for a process that matches a string:
+
+`ps aux | grep {{string}}`
+
+- List all processes of the current user in extra full format:
+
+`ps --user $(id -u) -F`
+
+- List all processes of the current user as a tree:
+
+`ps --user $(id -u) f`
+
+- Get the parent pid of a process:
+
+`ps -o ppid= -p {{pid}}`
diff --git a/tldr.cabal b/tldr.cabal
--- a/tldr.cabal
+++ b/tldr.cabal
@@ -1,5 +1,5 @@
 name:                tldr
-version:             0.3.0
+version:             0.3.1
 synopsis:            Haskell tldr client
 description:         Haskell tldr client with support for updating and viewing tldr pages.
 homepage:            https://github.com/psibi/tldr-hs#readme
@@ -10,7 +10,7 @@
 copyright:           2017 Sibi
 category:            Web
 build-type:          Simple
-extra-source-files:  README.md, CHANGELOG.md
+extra-source-files:  README.md, CHANGELOG.md, test/data/*.golden, test/data/*.md
 cabal-version:       >=1.10
 
 library
@@ -43,6 +43,8 @@
   main-is:             Spec.hs
   build-depends:       base
                      , tldr
+                     , tasty
+                     , tasty-golden
   ghc-options:         -threaded -rtsopts -with-rtsopts=-N
   default-language:    Haskell2010
 
