diff --git a/src/bindings/ruby/Gemfile b/src/bindings/ruby/Gemfile
index 9e7a9bec407e905ae865544939da80d4de239b28..df4281435fb46ad55a0f081e9ce583816586f2a5 100644
--- a/src/bindings/ruby/Gemfile
+++ b/src/bindings/ruby/Gemfile
@@ -3,5 +3,5 @@ source 'https://rubygems.org'
 gemspec
 
 group :test do
-  # ...
+  gem 'minitest', '~> 5.2'
 end
diff --git a/src/bindings/ruby/README.md b/src/bindings/ruby/README.md
index e69a504aae072f1d6d438c428c1d2ddbc640730e..61ce0b37958abc63e2d603cacfb58d1ea11c0fe4 100644
--- a/src/bindings/ruby/README.md
+++ b/src/bindings/ruby/README.md
@@ -16,6 +16,8 @@ Ruby bindings for [hammer](https://github.com/UpstandingHackers/hammer), a parsi
 
 3. Run `irb -I ./lib -r hammer` to open `irb` with hammer loaded.
 
+4. To run tests, just run `rake`.
+
 
 ## Installation
 
diff --git a/src/bindings/ruby/Rakefile b/src/bindings/ruby/Rakefile
new file mode 100644
index 0000000000000000000000000000000000000000..70b8662375affb9a9da753d4c2e434ef8748315f
--- /dev/null
+++ b/src/bindings/ruby/Rakefile
@@ -0,0 +1,7 @@
+require 'rake/testtask'
+
+Rake::TestTask.new do |t|
+  t.pattern = "test/*_test.rb"
+end
+
+task :default => [:test]
diff --git a/src/bindings/ruby/lib/hammer.rb b/src/bindings/ruby/lib/hammer.rb
index db987b4521a0c930a0c297ca5b39db42058c2b7c..bb0e2a2dafa4b14a31491730ebd1cb60e25b8694 100644
--- a/src/bindings/ruby/lib/hammer.rb
+++ b/src/bindings/ruby/lib/hammer.rb
@@ -7,79 +7,13 @@ require 'hammer/parser_builder'
 # people can use "require 'hammer-parser'" in their code.
 
 
-
-# TODO: Put tests in test/ directory.
-
-parser = Hammer::Parser.build do
-  token 'blah'
-  ch 'a'.ord
-  choice {
-    sequence {
-      token 'abc'
-    }
-    token 'def'
-  }
-end
-
-p parser
-
-if parser
-  p parser.parse 'blahaabcd'
-  p parser.parse 'blahadefd'
-  p parser.parse 'blahablad'
-  p parser.parse 'blaha'
-  p parser.parse 'blah'
-end
-
-parser = Hammer::Parser.build {
-  token 'Hello '
-  choice {
-    token 'Mom'
-    token 'Dad'
-  }
-  token '!'
-}
-p parser.parse 'Hello Mom!'
-
-parser = Hammer::ParserBuilder.new
-  .token('Hello ')
-  .choice(Hammer::Parser.token('Mom'), Hammer::Parser.token('Dad'))
-  .token('!')
-  .build
-p parser.parse 'Hello Mom!'
-
-h = Hammer::Parser
-parser = h.sequence(h.token('Hello '), h.choice(h.token('Mom'), h.token('Dad')), h.token('!'))
-p parser.parse 'Hello Mom!'
-
-s = 'blah'
-parser = h.token(s)
-p parser.parse 'BLAH' # => false
-s.upcase!
-p parser.parse 'BLAH' # => false
-
-
+# Leave this in for now to be able to play around with HParseResult in irb.
 x = nil
 parser = Hammer::Parser.build {
   token 'abc'
   x = indirect
   end_p
 }
-x.bind(h.token('abd'))
-
-p parser.parse 'abcabdabd'
-p parser.parse 'abcabd'
-p parser.parse 'abdabd'
-p parser.parse 'abd'
-
-#$r = parser.parse 'abcabd'
-
-
-# Test multibyte characters
-parser = Hammer::Parser.build {
-  token '今日'
-  end_p
-}
-
-p ($r = parser.parse('今日')) # should succeed
+x.bind(Hammer::Parser.token('abd'))
 
+$r = parser.parse 'abcabd'
diff --git a/src/bindings/ruby/test/parser_test.rb b/src/bindings/ruby/test/parser_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d3c4bbb1c0ead85eb94676bb76604d1e71a0336c
--- /dev/null
+++ b/src/bindings/ruby/test/parser_test.rb
@@ -0,0 +1,82 @@
+require 'bundler/setup'
+require 'hammer'
+require 'minitest/autorun'
+
+class ParserTest < Minitest::Test
+  def test_builder_1
+    parser = Hammer::Parser.build {
+      token 'blah'
+      ch 'a'.ord
+      choice {
+        sequence {
+          token 'abc'
+        }
+        token 'def'
+      }
+    }
+
+    refute_nil parser
+
+    refute_nil parser.parse('blahaabcd')
+    refute_nil parser.parse('blahadefd')
+    assert_nil parser.parse('blahablad')
+    assert_nil parser.parse('blaha')
+    assert_nil parser.parse('blah')
+  end
+
+  def test_builder_2
+    parser = Hammer::ParserBuilder.new
+      .token('Hello ')
+      .choice(Hammer::Parser.token('Mom'), Hammer::Parser.token('Dad'))
+      .token('!')
+      .build
+
+    refute_nil parser
+    refute_nil parser.parse('Hello Mom!')
+  end
+
+  def test_builder_3
+    h = Hammer::Parser
+    parser = h.sequence(h.token('Hello '), h.choice(h.token('Mom'), h.token('Dad')), h.token('!'))
+
+    refute_nil parser
+    refute_nil parser.parse('Hello Mom!')
+  end
+
+  def test_string_copied
+    s = 'blah'
+    parser = Hammer::Parser.token(s)
+
+    refute_equal s, 'BLAH'
+    assert_nil parser.parse('BLAH')
+
+    # parser still shouldn't match, even if we modify the string in-place
+    s.upcase!
+    assert_equal s, 'BLAH'
+    assert_nil parser.parse('BLAH')
+  end
+
+  def test_indirect
+    x = nil
+    parser = Hammer::Parser.build {
+      token 'abc'
+      x = indirect
+      end_p
+    }
+    x.bind(Hammer::Parser.token('abd'))
+
+    assert_nil parser.parse('abcabdabd')
+    refute_nil parser.parse('abcabd')
+    assert_nil parser.parse('abdabd')
+    assert_nil parser.parse('abc')
+  end
+
+  def test_multibyte_token
+    parser = Hammer::Parser.build {
+      token '今日'
+      end_p
+    }
+
+    refute_nil parser.parse('今日')
+  end
+end