From 3789a309554fd600caeae442f40881cf93eb3b54 Mon Sep 17 00:00:00 2001
From: Hannah Wolfe <erisds@gmail.com>
Date: Fri, 3 Apr 2020 20:05:19 +0100
Subject: [PATCH 1/2] chore: start testing on Node.js 12 and 13

- make sure we're aware of all compatibility issues
---
 integration-testing/multi-nodejs-test/test.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/integration-testing/multi-nodejs-test/test.sh b/integration-testing/multi-nodejs-test/test.sh
index 99682b45..4dd6d05a 100755
--- a/integration-testing/multi-nodejs-test/test.sh
+++ b/integration-testing/multi-nodejs-test/test.sh
@@ -17,7 +17,7 @@ cd "$( dirname "$( readlink -f "$0" )" )" || exit 1
 unset npm_config_prefix
 
 echo "Handlebars should be able to run in various versions of NodeJS"
-for i in  0.10 0.12 4 5 6 7 8 9 10 11 ; do
+for i in  0.10 0.12 4 5 6 7 8 9 10 11 12 13 ; do
     rm target node_modules package-lock.json -rf
     mkdir target
     nvm install "$i"

From 77825f8d3522356feb8e4160fac16344104d192b Mon Sep 17 00:00:00 2001
From: John Boehr <john@johnboehr.is>
Date: Mon, 4 May 2020 15:04:33 -0700
Subject: [PATCH 2/2] refator: In spec tests, use expectTemplate over equals
 and shouldThrow (#1683)

---
 spec/basic.js               |  790 ++++++++++-----------
 spec/blocks.js              |  625 ++++++++--------
 spec/builtins.js            |  890 +++++++++++------------
 spec/data.js                |  402 +++++------
 spec/env/common.js          |   47 +-
 spec/helpers.js             | 1335 ++++++++++++++---------------------
 spec/javascript-compiler.js |   20 +-
 spec/partials.js            | 1048 ++++++++++++---------------
 spec/regressions.js         |  461 ++++++------
 spec/security.js            |   66 +-
 spec/spec.js                |   22 +-
 spec/strict.js              |  262 +++----
 spec/string-params.js       |  347 ++++-----
 spec/subexpressions.js      |  438 ++++++------
 spec/track-ids.js           |  508 ++++++-------
 spec/utils.js               |    8 +-
 spec/whitespace-control.js  |  205 +++---
 17 files changed, 3350 insertions(+), 4124 deletions(-)

diff --git a/spec/basic.js b/spec/basic.js
index 65d22872..4c7afb70 100644
--- a/spec/basic.js
+++ b/spec/basic.js
@@ -6,117 +6,156 @@ beforeEach(function() {
 
 describe('basic context', function() {
   it('most basic', function() {
-    shouldCompileTo('{{foo}}', { foo: 'foo' }, 'foo');
+    expectTemplate('{{foo}}')
+      .withInput({ foo: 'foo' })
+      .toCompileTo('foo');
   });
 
   it('escaping', function() {
-    shouldCompileTo('\\{{foo}}', { foo: 'food' }, '{{foo}}');
-    shouldCompileTo('content \\{{foo}}', { foo: 'food' }, 'content {{foo}}');
-    shouldCompileTo('\\\\{{foo}}', { foo: 'food' }, '\\food');
-    shouldCompileTo('content \\\\{{foo}}', { foo: 'food' }, 'content \\food');
-    shouldCompileTo('\\\\ {{foo}}', { foo: 'food' }, '\\\\ food');
+    expectTemplate('\\{{foo}}')
+      .withInput({ foo: 'food' })
+      .toCompileTo('{{foo}}');
+
+    expectTemplate('content \\{{foo}}')
+      .withInput({ foo: 'food' })
+      .toCompileTo('content {{foo}}');
+
+    expectTemplate('\\\\{{foo}}')
+      .withInput({ foo: 'food' })
+      .toCompileTo('\\food');
+
+    expectTemplate('content \\\\{{foo}}')
+      .withInput({ foo: 'food' })
+      .toCompileTo('content \\food');
+
+    expectTemplate('\\\\ {{foo}}')
+      .withInput({ foo: 'food' })
+      .toCompileTo('\\\\ food');
   });
 
   it('compiling with a basic context', function() {
-    shouldCompileTo(
-      'Goodbye\n{{cruel}}\n{{world}}!',
-      { cruel: 'cruel', world: 'world' },
-      'Goodbye\ncruel\nworld!',
-      'It works if all the required keys are provided'
-    );
+    expectTemplate('Goodbye\n{{cruel}}\n{{world}}!')
+      .withInput({
+        cruel: 'cruel',
+        world: 'world'
+      })
+      .withMessage('It works if all the required keys are provided')
+      .toCompileTo('Goodbye\ncruel\nworld!');
   });
 
   it('compiling with a string context', function() {
-    shouldCompileTo('{{.}}{{length}}', 'bye', 'bye3');
+    expectTemplate('{{.}}{{length}}')
+      .withInput('bye')
+      .toCompileTo('bye3');
   });
 
   it('compiling with an undefined context', function() {
-    shouldCompileTo(
-      'Goodbye\n{{cruel}}\n{{world.bar}}!',
-      undefined,
-      'Goodbye\n\n!'
-    );
+    expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!')
+      .withInput(undefined)
+      .toCompileTo('Goodbye\n\n!');
 
-    shouldCompileTo(
-      '{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}',
-      undefined,
-      'Goodbye'
-    );
+    expectTemplate('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}')
+      .withInput(undefined)
+      .toCompileTo('Goodbye');
   });
 
   it('comments', function() {
-    shouldCompileTo(
-      '{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
-      { cruel: 'cruel', world: 'world' },
-      'Goodbye\ncruel\nworld!',
-      'comments are ignored'
+    expectTemplate('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!')
+      .withInput({
+        cruel: 'cruel',
+        world: 'world'
+      })
+      .withMessage('comments are ignored')
+      .toCompileTo('Goodbye\ncruel\nworld!');
+
+    expectTemplate('    {{~! comment ~}}      blah').toCompileTo('blah');
+
+    expectTemplate('    {{~!-- long-comment --~}}      blah').toCompileTo(
+      'blah'
     );
 
-    shouldCompileTo('    {{~! comment ~}}      blah', {}, 'blah');
-    shouldCompileTo('    {{~!-- long-comment --~}}      blah', {}, 'blah');
-    shouldCompileTo('    {{! comment ~}}      blah', {}, '    blah');
-    shouldCompileTo('    {{!-- long-comment --~}}      blah', {}, '    blah');
-    shouldCompileTo('    {{~! comment}}      blah', {}, '      blah');
-    shouldCompileTo('    {{~!-- long-comment --}}      blah', {}, '      blah');
-  });
+    expectTemplate('    {{! comment ~}}      blah').toCompileTo('    blah');
 
-  it('boolean', function() {
-    var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
-    shouldCompileTo(
-      string,
-      { goodbye: true, world: 'world' },
-      'GOODBYE cruel world!',
-      'booleans show the contents when true'
+    expectTemplate('    {{!-- long-comment --~}}      blah').toCompileTo(
+      '    blah'
     );
 
-    shouldCompileTo(
-      string,
-      { goodbye: false, world: 'world' },
-      'cruel world!',
-      'booleans do not show the contents when false'
+    expectTemplate('    {{~! comment}}      blah').toCompileTo('      blah');
+
+    expectTemplate('    {{~!-- long-comment --}}      blah').toCompileTo(
+      '      blah'
     );
   });
 
+  it('boolean', function() {
+    var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
+    expectTemplate(string)
+      .withInput({
+        goodbye: true,
+        world: 'world'
+      })
+      .withMessage('booleans show the contents when true')
+      .toCompileTo('GOODBYE cruel world!');
+
+    expectTemplate(string)
+      .withInput({
+        goodbye: false,
+        world: 'world'
+      })
+      .withMessage('booleans do not show the contents when false')
+      .toCompileTo('cruel world!');
+  });
+
   it('zeros', function() {
-    shouldCompileTo(
-      'num1: {{num1}}, num2: {{num2}}',
-      { num1: 42, num2: 0 },
-      'num1: 42, num2: 0'
-    );
-    shouldCompileTo('num: {{.}}', 0, 'num: 0');
-    shouldCompileTo('num: {{num1/num2}}', { num1: { num2: 0 } }, 'num: 0');
+    expectTemplate('num1: {{num1}}, num2: {{num2}}')
+      .withInput({
+        num1: 42,
+        num2: 0
+      })
+      .toCompileTo('num1: 42, num2: 0');
+
+    expectTemplate('num: {{.}}')
+      .withInput(0)
+      .toCompileTo('num: 0');
+
+    expectTemplate('num: {{num1/num2}}')
+      .withInput({ num1: { num2: 0 } })
+      .toCompileTo('num: 0');
   });
+
   it('false', function() {
     /* eslint-disable no-new-wrappers */
-    shouldCompileTo(
-      'val1: {{val1}}, val2: {{val2}}',
-      { val1: false, val2: new Boolean(false) },
-      'val1: false, val2: false'
-    );
-    shouldCompileTo('val: {{.}}', false, 'val: false');
-    shouldCompileTo(
-      'val: {{val1/val2}}',
-      { val1: { val2: false } },
-      'val: false'
-    );
-
-    shouldCompileTo(
-      'val1: {{{val1}}}, val2: {{{val2}}}',
-      { val1: false, val2: new Boolean(false) },
-      'val1: false, val2: false'
-    );
-    shouldCompileTo(
-      'val: {{{val1/val2}}}',
-      { val1: { val2: false } },
-      'val: false'
-    );
+    expectTemplate('val1: {{val1}}, val2: {{val2}}')
+      .withInput({
+        val1: false,
+        val2: new Boolean(false)
+      })
+      .toCompileTo('val1: false, val2: false');
+
+    expectTemplate('val: {{.}}')
+      .withInput(false)
+      .toCompileTo('val: false');
+
+    expectTemplate('val: {{val1/val2}}')
+      .withInput({ val1: { val2: false } })
+      .toCompileTo('val: false');
+
+    expectTemplate('val1: {{{val1}}}, val2: {{{val2}}}')
+      .withInput({
+        val1: false,
+        val2: new Boolean(false)
+      })
+      .toCompileTo('val1: false, val2: false');
+
+    expectTemplate('val: {{{val1/val2}}}')
+      .withInput({ val1: { val2: false } })
+      .toCompileTo('val: false');
     /* eslint-enable */
   });
 
   it('should handle undefined and null', function() {
-    shouldCompileTo(
-      '{{awesome undefined null}}',
-      {
+    expectTemplate('{{awesome undefined null}}')
+      .withInput({
         awesome: function(_undefined, _null, options) {
           return (
             (_undefined === undefined) +
@@ -126,373 +165,325 @@ describe('basic context', function() {
             typeof options
           );
         }
-      },
-      'true true object'
-    );
-    shouldCompileTo(
-      '{{undefined}}',
-      {
+      })
+      .toCompileTo('true true object');
+
+    expectTemplate('{{undefined}}')
+      .withInput({
         undefined: function() {
           return 'undefined!';
         }
-      },
-      'undefined!'
-    );
-    shouldCompileTo(
-      '{{null}}',
-      {
+      })
+      .toCompileTo('undefined!');
+
+    expectTemplate('{{null}}')
+      .withInput({
         null: function() {
           return 'null!';
         }
-      },
-      'null!'
-    );
+      })
+      .toCompileTo('null!');
   });
 
   it('newlines', function() {
-    shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
-    shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
+    expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest");
+
+    expectTemplate("Alan's\rTest").toCompileTo("Alan's\rTest");
   });
 
   it('escaping text', function() {
-    shouldCompileTo(
-      "Awesome's",
-      {},
-      "Awesome's",
-      "text is escaped so that it doesn't get caught on single quotes"
-    );
-    shouldCompileTo(
-      'Awesome\\',
-      {},
-      'Awesome\\',
-      "text is escaped so that the closing quote can't be ignored"
-    );
-    shouldCompileTo(
-      'Awesome\\\\ foo',
-      {},
-      'Awesome\\\\ foo',
-      "text is escaped so that it doesn't mess up backslashes"
-    );
-    shouldCompileTo(
-      'Awesome {{foo}}',
-      { foo: '\\' },
-      'Awesome \\',
-      "text is escaped so that it doesn't mess up backslashes"
-    );
-    shouldCompileTo(
-      " ' ' ",
-      {},
-      " ' ' ",
-      'double quotes never produce invalid javascript'
-    );
+    expectTemplate("Awesome's")
+      .withMessage(
+        "text is escaped so that it doesn't get caught on single quotes"
+      )
+      .toCompileTo("Awesome's");
+
+    expectTemplate('Awesome\\')
+      .withMessage("text is escaped so that the closing quote can't be ignored")
+      .toCompileTo('Awesome\\');
+
+    expectTemplate('Awesome\\\\ foo')
+      .withMessage("text is escaped so that it doesn't mess up backslashes")
+      .toCompileTo('Awesome\\\\ foo');
+
+    expectTemplate('Awesome {{foo}}')
+      .withInput({ foo: '\\' })
+      .withMessage("text is escaped so that it doesn't mess up backslashes")
+      .toCompileTo('Awesome \\');
+
+    expectTemplate(" ' ' ")
+      .withMessage('double quotes never produce invalid javascript')
+      .toCompileTo(" ' ' ");
   });
 
   it('escaping expressions', function() {
-    shouldCompileTo(
-      '{{{awesome}}}',
-      { awesome: "&'\\<>" },
-      "&'\\<>",
-      "expressions with 3 handlebars aren't escaped"
-    );
+    expectTemplate('{{{awesome}}}')
+      .withInput({ awesome: "&'\\<>" })
+      .withMessage("expressions with 3 handlebars aren't escaped")
+      .toCompileTo("&'\\<>");
 
-    shouldCompileTo(
-      '{{&awesome}}',
-      { awesome: "&'\\<>" },
-      "&'\\<>",
-      "expressions with {{& handlebars aren't escaped"
-    );
+    expectTemplate('{{&awesome}}')
+      .withInput({ awesome: "&'\\<>" })
+      .withMessage("expressions with {{& handlebars aren't escaped")
+      .toCompileTo("&'\\<>");
 
-    shouldCompileTo(
-      '{{awesome}}',
-      { awesome: '&"\'`\\<>' },
-      '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
-      'by default expressions should be escaped'
-    );
+    expectTemplate('{{awesome}}')
+      .withInput({ awesome: '&"\'`\\<>' })
+      .withMessage('by default expressions should be escaped')
+      .toCompileTo('&amp;&quot;&#x27;&#x60;\\&lt;&gt;');
 
-    shouldCompileTo(
-      '{{awesome}}',
-      { awesome: 'Escaped, <b> looks like: &lt;b&gt;' },
-      'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;',
-      'escaping should properly handle amperstands'
-    );
+    expectTemplate('{{awesome}}')
+      .withInput({ awesome: 'Escaped, <b> looks like: &lt;b&gt;' })
+      .withMessage('escaping should properly handle amperstands')
+      .toCompileTo('Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;');
   });
 
   it("functions returning safestrings shouldn't be escaped", function() {
-    var hash = {
-      awesome: function() {
-        return new Handlebars.SafeString("&'\\<>");
-      }
-    };
-    shouldCompileTo(
-      '{{awesome}}',
-      hash,
-      "&'\\<>",
-      "functions returning safestrings aren't escaped"
-    );
+    expectTemplate('{{awesome}}')
+      .withInput({
+        awesome: function() {
+          return new Handlebars.SafeString("&'\\<>");
+        }
+      })
+      .withMessage("functions returning safestrings aren't escaped")
+      .toCompileTo("&'\\<>");
   });
 
   it('functions', function() {
-    shouldCompileTo(
-      '{{awesome}}',
-      {
+    expectTemplate('{{awesome}}')
+      .withInput({
         awesome: function() {
           return 'Awesome';
         }
-      },
-      'Awesome',
-      'functions are called and render their output'
-    );
-    shouldCompileTo(
-      '{{awesome}}',
-      {
+      })
+      .withMessage('functions are called and render their output')
+      .toCompileTo('Awesome');
+
+    expectTemplate('{{awesome}}')
+      .withInput({
         awesome: function() {
           return this.more;
         },
         more: 'More awesome'
-      },
-      'More awesome',
-      'functions are bound to the context'
-    );
+      })
+      .withMessage('functions are bound to the context')
+      .toCompileTo('More awesome');
   });
 
   it('functions with context argument', function() {
-    shouldCompileTo(
-      '{{awesome frank}}',
-      {
+    expectTemplate('{{awesome frank}}')
+      .withInput({
         awesome: function(context) {
           return context;
         },
         frank: 'Frank'
-      },
-      'Frank',
-      'functions are called with context arguments'
-    );
+      })
+      .withMessage('functions are called with context arguments')
+      .toCompileTo('Frank');
   });
+
   it('pathed functions with context argument', function() {
-    shouldCompileTo(
-      '{{bar.awesome frank}}',
-      {
+    expectTemplate('{{bar.awesome frank}}')
+      .withInput({
         bar: {
           awesome: function(context) {
             return context;
           }
         },
         frank: 'Frank'
-      },
-      'Frank',
-      'functions are called with context arguments'
-    );
+      })
+      .withMessage('functions are called with context arguments')
+      .toCompileTo('Frank');
   });
+
   it('depthed functions with context argument', function() {
-    shouldCompileTo(
-      '{{#with frank}}{{../awesome .}}{{/with}}',
-      {
+    expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}')
+      .withInput({
         awesome: function(context) {
           return context;
         },
         frank: 'Frank'
-      },
-      'Frank',
-      'functions are called with context arguments'
-    );
+      })
+      .withMessage('functions are called with context arguments')
+      .toCompileTo('Frank');
   });
 
   it('block functions with context argument', function() {
-    shouldCompileTo(
-      '{{#awesome 1}}inner {{.}}{{/awesome}}',
-      {
+    expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}')
+      .withInput({
         awesome: function(context, options) {
           return options.fn(context);
         }
-      },
-      'inner 1',
-      'block functions are called with context and options'
-    );
+      })
+      .withMessage('block functions are called with context and options')
+      .toCompileTo('inner 1');
   });
 
   it('depthed block functions with context argument', function() {
-    shouldCompileTo(
-      '{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}',
-      {
+    expectTemplate(
+      '{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}'
+    )
+      .withInput({
         value: true,
         awesome: function(context, options) {
           return options.fn(context);
         }
-      },
-      'inner 1',
-      'block functions are called with context and options'
-    );
+      })
+      .withMessage('block functions are called with context and options')
+      .toCompileTo('inner 1');
   });
 
   it('block functions without context argument', function() {
-    shouldCompileTo(
-      '{{#awesome}}inner{{/awesome}}',
-      {
+    expectTemplate('{{#awesome}}inner{{/awesome}}')
+      .withInput({
         awesome: function(options) {
           return options.fn(this);
         }
-      },
-      'inner',
-      'block functions are called with options'
-    );
+      })
+      .withMessage('block functions are called with options')
+      .toCompileTo('inner');
   });
+
   it('pathed block functions without context argument', function() {
-    shouldCompileTo(
-      '{{#foo.awesome}}inner{{/foo.awesome}}',
-      {
+    expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}')
+      .withInput({
         foo: {
           awesome: function() {
             return this;
           }
         }
-      },
-      'inner',
-      'block functions are called with options'
-    );
+      })
+      .withMessage('block functions are called with options')
+      .toCompileTo('inner');
   });
+
   it('depthed block functions without context argument', function() {
-    shouldCompileTo(
-      '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}',
-      {
+    expectTemplate(
+      '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}'
+    )
+      .withInput({
         value: true,
         awesome: function() {
           return this;
         }
-      },
-      'inner',
-      'block functions are called with options'
-    );
+      })
+      .withMessage('block functions are called with options')
+      .toCompileTo('inner');
   });
 
   it('paths with hyphens', function() {
-    shouldCompileTo(
-      '{{foo-bar}}',
-      { 'foo-bar': 'baz' },
-      'baz',
-      'Paths can contain hyphens (-)'
-    );
-    shouldCompileTo(
-      '{{foo.foo-bar}}',
-      { foo: { 'foo-bar': 'baz' } },
-      'baz',
-      'Paths can contain hyphens (-)'
-    );
-    shouldCompileTo(
-      '{{foo/foo-bar}}',
-      { foo: { 'foo-bar': 'baz' } },
-      'baz',
-      'Paths can contain hyphens (-)'
-    );
+    expectTemplate('{{foo-bar}}')
+      .withInput({ 'foo-bar': 'baz' })
+      .withMessage('Paths can contain hyphens (-)')
+      .toCompileTo('baz');
+
+    expectTemplate('{{foo.foo-bar}}')
+      .withInput({ foo: { 'foo-bar': 'baz' } })
+      .withMessage('Paths can contain hyphens (-)')
+      .toCompileTo('baz');
+
+    expectTemplate('{{foo/foo-bar}}')
+      .withInput({ foo: { 'foo-bar': 'baz' } })
+      .withMessage('Paths can contain hyphens (-)')
+      .toCompileTo('baz');
   });
 
   it('nested paths', function() {
-    shouldCompileTo(
-      'Goodbye {{alan/expression}} world!',
-      { alan: { expression: 'beautiful' } },
-      'Goodbye beautiful world!',
-      'Nested paths access nested objects'
-    );
+    expectTemplate('Goodbye {{alan/expression}} world!')
+      .withInput({ alan: { expression: 'beautiful' } })
+      .withMessage('Nested paths access nested objects')
+      .toCompileTo('Goodbye beautiful world!');
   });
 
   it('nested paths with empty string value', function() {
-    shouldCompileTo(
-      'Goodbye {{alan/expression}} world!',
-      { alan: { expression: '' } },
-      'Goodbye  world!',
-      'Nested paths access nested objects with empty string'
-    );
+    expectTemplate('Goodbye {{alan/expression}} world!')
+      .withInput({ alan: { expression: '' } })
+      .withMessage('Nested paths access nested objects with empty string')
+      .toCompileTo('Goodbye  world!');
   });
 
   it('literal paths', function() {
-    shouldCompileTo(
-      'Goodbye {{[@alan]/expression}} world!',
-      { '@alan': { expression: 'beautiful' } },
-      'Goodbye beautiful world!',
-      'Literal paths can be used'
-    );
-    shouldCompileTo(
-      'Goodbye {{[foo bar]/expression}} world!',
-      { 'foo bar': { expression: 'beautiful' } },
-      'Goodbye beautiful world!',
-      'Literal paths can be used'
-    );
+    expectTemplate('Goodbye {{[@alan]/expression}} world!')
+      .withInput({ '@alan': { expression: 'beautiful' } })
+      .withMessage('Literal paths can be used')
+      .toCompileTo('Goodbye beautiful world!');
+
+    expectTemplate('Goodbye {{[foo bar]/expression}} world!')
+      .withInput({ 'foo bar': { expression: 'beautiful' } })
+      .withMessage('Literal paths can be used')
+      .toCompileTo('Goodbye beautiful world!');
   });
 
   it('literal references', function() {
-    shouldCompileTo(
-      'Goodbye {{[foo bar]}} world!',
-      { 'foo bar': 'beautiful' },
-      'Goodbye beautiful world!'
-    );
-    shouldCompileTo(
-      'Goodbye {{"foo bar"}} world!',
-      { 'foo bar': 'beautiful' },
-      'Goodbye beautiful world!'
-    );
-    shouldCompileTo(
-      "Goodbye {{'foo bar'}} world!",
-      { 'foo bar': 'beautiful' },
-      'Goodbye beautiful world!'
-    );
-    shouldCompileTo(
-      'Goodbye {{"foo[bar"}} world!',
-      { 'foo[bar': 'beautiful' },
-      'Goodbye beautiful world!'
-    );
-    shouldCompileTo(
-      'Goodbye {{"foo\'bar"}} world!',
-      { "foo'bar": 'beautiful' },
-      'Goodbye beautiful world!'
-    );
-    shouldCompileTo(
-      "Goodbye {{'foo\"bar'}} world!",
-      { 'foo"bar': 'beautiful' },
-      'Goodbye beautiful world!'
-    );
+    expectTemplate('Goodbye {{[foo bar]}} world!')
+      .withInput({ 'foo bar': 'beautiful' })
+      .toCompileTo('Goodbye beautiful world!');
+
+    expectTemplate('Goodbye {{"foo bar"}} world!')
+      .withInput({ 'foo bar': 'beautiful' })
+      .toCompileTo('Goodbye beautiful world!');
+
+    expectTemplate("Goodbye {{'foo bar'}} world!")
+      .withInput({ 'foo bar': 'beautiful' })
+      .toCompileTo('Goodbye beautiful world!');
+
+    expectTemplate('Goodbye {{"foo[bar"}} world!')
+      .withInput({ 'foo[bar': 'beautiful' })
+      .toCompileTo('Goodbye beautiful world!');
+
+    expectTemplate('Goodbye {{"foo\'bar"}} world!')
+      .withInput({ "foo'bar": 'beautiful' })
+      .toCompileTo('Goodbye beautiful world!');
+
+    expectTemplate("Goodbye {{'foo\"bar'}} world!")
+      .withInput({ 'foo"bar': 'beautiful' })
+      .toCompileTo('Goodbye beautiful world!');
   });
 
   it("that current context path ({{.}}) doesn't hit helpers", function() {
-    shouldCompileTo('test: {{.}}', [null, { helper: 'awesome' }], 'test: ');
+    expectTemplate('test: {{.}}')
+      .withInput(null)
+      .withHelpers({ helper: 'awesome' })
+      .toCompileTo('test: ');
   });
 
   it('complex but empty paths', function() {
-    shouldCompileTo('{{person/name}}', { person: { name: null } }, '');
-    shouldCompileTo('{{person/name}}', { person: {} }, '');
+    expectTemplate('{{person/name}}')
+      .withInput({ person: { name: null } })
+      .toCompileTo('');
+
+    expectTemplate('{{person/name}}')
+      .withInput({ person: {} })
+      .toCompileTo('');
   });
 
   it('this keyword in paths', function() {
-    var string = '{{#goodbyes}}{{this}}{{/goodbyes}}';
-    var hash = { goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] };
-    shouldCompileTo(
-      string,
-      hash,
-      'goodbyeGoodbyeGOODBYE',
-      'This keyword in paths evaluates to current context'
-    );
+    expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}')
+      .withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
+      .withMessage('This keyword in paths evaluates to current context')
+      .toCompileTo('goodbyeGoodbyeGOODBYE');
 
-    string = '{{#hellos}}{{this/text}}{{/hellos}}';
-    hash = {
-      hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
-    };
-    shouldCompileTo(
-      string,
-      hash,
-      'helloHelloHELLO',
-      'This keyword evaluates in more complex paths'
-    );
+    expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}')
+      .withInput({
+        hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
+      })
+      .withMessage('This keyword evaluates in more complex paths')
+      .toCompileTo('helloHelloHELLO');
   });
 
   it('this keyword nested inside path', function() {
-    shouldThrow(
-      function() {
-        CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}');
-      },
+    expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow(
       Error,
       'Invalid path: text/this - 1:13'
     );
 
-    shouldCompileTo('{{[this]}}', { this: 'bar' }, 'bar');
-    shouldCompileTo('{{text/[this]}}', { text: { this: 'bar' } }, 'bar');
+    expectTemplate('{{[this]}}')
+      .withInput({ this: 'bar' })
+      .toCompileTo('bar');
+
+    expectTemplate('{{text/[this]}}')
+      .withInput({ text: { this: 'bar' } })
+      .toCompileTo('bar');
   });
 
   it('this keyword in helpers', function() {
@@ -501,108 +492,105 @@ describe('basic context', function() {
         return 'bar ' + value;
       }
     };
-    var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}';
-    var hash = { goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] };
-    shouldCompileTo(
-      string,
-      [hash, helpers],
-      'bar goodbyebar Goodbyebar GOODBYE',
-      'This keyword in paths evaluates to current context'
-    );
 
-    string = '{{#hellos}}{{foo this/text}}{{/hellos}}';
-    hash = {
-      hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
-    };
-    shouldCompileTo(
-      string,
-      [hash, helpers],
-      'bar hellobar Hellobar HELLO',
-      'This keyword evaluates in more complex paths'
-    );
+    expectTemplate('{{#goodbyes}}{{foo this}}{{/goodbyes}}')
+      .withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
+      .withHelpers(helpers)
+      .withMessage('This keyword in paths evaluates to current context')
+      .toCompileTo('bar goodbyebar Goodbyebar GOODBYE');
+
+    expectTemplate('{{#hellos}}{{foo this/text}}{{/hellos}}')
+      .withInput({
+        hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
+      })
+      .withHelpers(helpers)
+      .withMessage('This keyword evaluates in more complex paths')
+      .toCompileTo('bar hellobar Hellobar HELLO');
   });
 
   it('this keyword nested inside helpers param', function() {
-    var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}';
-    shouldThrow(
-      function() {
-        CompilerContext.compile(string);
-      },
+    expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow(
       Error,
       'Invalid path: text/this - 1:17'
     );
 
-    shouldCompileTo(
-      '{{foo [this]}}',
-      {
+    expectTemplate('{{foo [this]}}')
+      .withInput({
         foo: function(value) {
           return value;
         },
         this: 'bar'
-      },
-      'bar'
-    );
-    shouldCompileTo(
-      '{{foo text/[this]}}',
-      {
+      })
+      .toCompileTo('bar');
+
+    expectTemplate('{{foo text/[this]}}')
+      .withInput({
         foo: function(value) {
           return value;
         },
         text: { this: 'bar' }
-      },
-      'bar'
-    );
+      })
+      .toCompileTo('bar');
   });
 
   it('pass string literals', function() {
-    shouldCompileTo('{{"foo"}}', {}, '');
-    shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar');
-    shouldCompileTo(
-      '{{#"foo"}}{{.}}{{/"foo"}}',
-      { foo: ['bar', 'baz'] },
-      'barbaz'
-    );
+    expectTemplate('{{"foo"}}').toCompileTo('');
+
+    expectTemplate('{{"foo"}}')
+      .withInput({ foo: 'bar' })
+      .toCompileTo('bar');
+
+    expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}')
+      .withInput({
+        foo: ['bar', 'baz']
+      })
+      .toCompileTo('barbaz');
   });
 
   it('pass number literals', function() {
-    shouldCompileTo('{{12}}', {}, '');
-    shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar');
-    shouldCompileTo('{{12.34}}', {}, '');
-    shouldCompileTo('{{12.34}}', { '12.34': 'bar' }, 'bar');
-    shouldCompileTo(
-      '{{12.34 1}}',
-      {
+    expectTemplate('{{12}}').toCompileTo('');
+
+    expectTemplate('{{12}}')
+      .withInput({ '12': 'bar' })
+      .toCompileTo('bar');
+
+    expectTemplate('{{12.34}}').toCompileTo('');
+
+    expectTemplate('{{12.34}}')
+      .withInput({ '12.34': 'bar' })
+      .toCompileTo('bar');
+
+    expectTemplate('{{12.34 1}}')
+      .withInput({
         '12.34': function(arg) {
           return 'bar' + arg;
         }
-      },
-      'bar1'
-    );
+      })
+      .toCompileTo('bar1');
   });
 
   it('pass boolean literals', function() {
-    shouldCompileTo('{{true}}', {}, '');
-    shouldCompileTo('{{true}}', { '': 'foo' }, '');
-    shouldCompileTo('{{false}}', { false: 'foo' }, 'foo');
+    expectTemplate('{{true}}').toCompileTo('');
+
+    expectTemplate('{{true}}')
+      .withInput({ '': 'foo' })
+      .toCompileTo('');
+
+    expectTemplate('{{false}}')
+      .withInput({ false: 'foo' })
+      .toCompileTo('foo');
   });
 
   it('should handle literals in subexpression', function() {
-    var helpers = {
-      foo: function(arg) {
+    expectTemplate('{{foo (false)}}')
+      .withInput({
+        false: function() {
+          return 'bar';
+        }
+      })
+      .withHelper('foo', function(arg) {
         return arg;
-      }
-    };
-    shouldCompileTo(
-      '{{foo (false)}}',
-      [
-        {
-          false: function() {
-            return 'bar';
-          }
-        },
-        helpers
-      ],
-      'bar'
-    );
+      })
+      .toCompileTo('bar');
   });
 });
diff --git a/spec/blocks.js b/spec/blocks.js
index ad534aab..f1565542 100644
--- a/spec/blocks.js
+++ b/spec/blocks.js
@@ -1,455 +1,405 @@
 describe('blocks', function() {
   it('array', function() {
     var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
-    var hash = {
-      goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
-      world: 'world'
-    };
-    shouldCompileTo(
-      string,
-      hash,
-      'goodbye! Goodbye! GOODBYE! cruel world!',
-      'Arrays iterate over the contents when not empty'
-    );
-
-    shouldCompileTo(
-      string,
-      { goodbyes: [], world: 'world' },
-      'cruel world!',
-      'Arrays ignore the contents when empty'
-    );
+
+    expectTemplate(string)
+      .withInput({
+        goodbyes: [
+          { text: 'goodbye' },
+          { text: 'Goodbye' },
+          { text: 'GOODBYE' }
+        ],
+        world: 'world'
+      })
+      .withMessage('Arrays iterate over the contents when not empty')
+      .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
+
+    expectTemplate(string)
+      .withInput({
+        goodbyes: [],
+        world: 'world'
+      })
+      .withMessage('Arrays ignore the contents when empty')
+      .toCompileTo('cruel world!');
   });
 
   it('array without data', function() {
-    var string =
-      '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
-    var hash = {
-      goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
-      world: 'world'
-    };
-    shouldCompileTo(
-      string,
-      [hash, , , false],
-      'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE'
-    );
+    expectTemplate(
+      '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}'
+    )
+      .withInput({
+        goodbyes: [
+          { text: 'goodbye' },
+          { text: 'Goodbye' },
+          { text: 'GOODBYE' }
+        ],
+        world: 'world'
+      })
+      .withCompileOptions({ compat: false })
+      .toCompileTo('goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
   });
 
   it('array with @index', function() {
-    var string =
-      '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!';
-    var hash = {
-      goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
-      world: 'world'
-    };
-
-    var template = CompilerContext.compile(string);
-    var result = template(hash);
-
-    equal(
-      result,
-      '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
-      'The @index variable is used'
-    );
+    expectTemplate(
+      '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!'
+    )
+      .withInput({
+        goodbyes: [
+          { text: 'goodbye' },
+          { text: 'Goodbye' },
+          { text: 'GOODBYE' }
+        ],
+        world: 'world'
+      })
+      .withMessage('The @index variable is used')
+      .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
   });
 
   it('empty block', function() {
     var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
-    var hash = {
-      goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
-      world: 'world'
-    };
-    shouldCompileTo(
-      string,
-      hash,
-      'cruel world!',
-      'Arrays iterate over the contents when not empty'
-    );
-
-    shouldCompileTo(
-      string,
-      { goodbyes: [], world: 'world' },
-      'cruel world!',
-      'Arrays ignore the contents when empty'
-    );
+
+    expectTemplate(string)
+      .withInput({
+        goodbyes: [
+          { text: 'goodbye' },
+          { text: 'Goodbye' },
+          { text: 'GOODBYE' }
+        ],
+        world: 'world'
+      })
+      .withMessage('Arrays iterate over the contents when not empty')
+      .toCompileTo('cruel world!');
+
+    expectTemplate(string)
+      .withInput({
+        goodbyes: [],
+        world: 'world'
+      })
+      .withMessage('Arrays ignore the contents when empty')
+      .toCompileTo('cruel world!');
   });
 
   it('block with complex lookup', function() {
-    var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}';
-    var hash = {
-      name: 'Alan',
-      goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
-    };
-
-    shouldCompileTo(
-      string,
-      hash,
-      'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ',
-      'Templates can access variables in contexts up the stack with relative path syntax'
-    );
+    expectTemplate('{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}')
+      .withInput({
+        name: 'Alan',
+        goodbyes: [
+          { text: 'goodbye' },
+          { text: 'Goodbye' },
+          { text: 'GOODBYE' }
+        ]
+      })
+      .withMessage(
+        'Templates can access variables in contexts up the stack with relative path syntax'
+      )
+      .toCompileTo(
+        'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! '
+      );
   });
 
   it('multiple blocks with complex lookup', function() {
-    var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}';
-    var hash = {
-      name: 'Alan',
-      goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
-    };
-
-    shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
+    expectTemplate('{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}')
+      .withInput({
+        name: 'Alan',
+        goodbyes: [
+          { text: 'goodbye' },
+          { text: 'Goodbye' },
+          { text: 'GOODBYE' }
+        ]
+      })
+      .toCompileTo('AlanAlanAlanAlanAlanAlan');
   });
 
   it('block with complex lookup using nested context', function() {
-    var string = '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}';
-
-    shouldThrow(function() {
-      CompilerContext.compile(string);
-    }, Error);
+    expectTemplate(
+      '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}'
+    ).toThrow(Error);
   });
 
   it('block with deep nested complex lookup', function() {
-    var string =
-      '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}';
-    var hash = {
-      omg: 'OMG!',
-      outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }]
-    };
-
-    shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!');
+    expectTemplate(
+      '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}'
+    )
+      .withInput({
+        omg: 'OMG!',
+        outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }]
+      })
+      .toCompileTo('Goodbye cruel sad OMG!');
   });
 
   it('works with cached blocks', function() {
-    var template = CompilerContext.compile(
-      '{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}',
-      { data: false }
-    );
-
-    var result = template({
-      person: [
-        { first: 'Alan', last: 'Johnson' },
-        { first: 'Alan', last: 'Johnson' }
-      ]
-    });
-    equals(result, 'Alan JohnsonAlan Johnson');
+    expectTemplate(
+      '{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}'
+    )
+      .withCompileOptions({ data: false })
+      .withInput({
+        person: [
+          { first: 'Alan', last: 'Johnson' },
+          { first: 'Alan', last: 'Johnson' }
+        ]
+      })
+      .toCompileTo('Alan JohnsonAlan Johnson');
   });
 
   describe('inverted sections', function() {
     it('inverted sections with unset value', function() {
-      var string =
-        '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
-      var hash = {};
-      shouldCompileTo(
-        string,
-        hash,
-        'Right On!',
-        "Inverted section rendered when value isn't set."
-      );
+      expectTemplate(
+        '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
+      )
+        .withMessage("Inverted section rendered when value isn't set.")
+        .toCompileTo('Right On!');
     });
 
     it('inverted section with false value', function() {
-      var string =
-        '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
-      var hash = { goodbyes: false };
-      shouldCompileTo(
-        string,
-        hash,
-        'Right On!',
-        'Inverted section rendered when value is false.'
-      );
+      expectTemplate(
+        '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
+      )
+        .withInput({ goodbyes: false })
+        .withMessage('Inverted section rendered when value is false.')
+        .toCompileTo('Right On!');
     });
 
     it('inverted section with empty set', function() {
-      var string =
-        '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
-      var hash = { goodbyes: [] };
-      shouldCompileTo(
-        string,
-        hash,
-        'Right On!',
-        'Inverted section rendered when value is empty set.'
-      );
+      expectTemplate(
+        '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
+      )
+        .withInput({ goodbyes: [] })
+        .withMessage('Inverted section rendered when value is empty set.')
+        .toCompileTo('Right On!');
     });
 
     it('block inverted sections', function() {
-      shouldCompileTo(
-        '{{#people}}{{name}}{{^}}{{none}}{{/people}}',
-        { none: 'No people' },
-        'No people'
-      );
+      expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}')
+        .withInput({ none: 'No people' })
+        .toCompileTo('No people');
     });
+
     it('chained inverted sections', function() {
-      shouldCompileTo(
-        '{{#people}}{{name}}{{else if none}}{{none}}{{/people}}',
-        { none: 'No people' },
-        'No people'
-      );
-      shouldCompileTo(
-        '{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}',
-        { none: 'No people' },
-        'No people'
-      );
-      shouldCompileTo(
-        '{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}',
-        { none: 'No people' },
-        'No people'
-      );
+      expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}')
+        .withInput({ none: 'No people' })
+        .toCompileTo('No people');
+
+      expectTemplate(
+        '{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}'
+      )
+        .withInput({ none: 'No people' })
+        .toCompileTo('No people');
+
+      expectTemplate(
+        '{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}'
+      )
+        .withInput({ none: 'No people' })
+        .toCompileTo('No people');
     });
+
     it('chained inverted sections with mismatch', function() {
-      shouldThrow(function() {
-        shouldCompileTo(
-          '{{#people}}{{name}}{{else if none}}{{none}}{{/if}}',
-          { none: 'No people' },
-          'No people'
-        );
-      }, Error);
+      expectTemplate(
+        '{{#people}}{{name}}{{else if none}}{{none}}{{/if}}'
+      ).toThrow(Error);
     });
 
     it('block inverted sections with empty arrays', function() {
-      shouldCompileTo(
-        '{{#people}}{{name}}{{^}}{{none}}{{/people}}',
-        { none: 'No people', people: [] },
-        'No people'
-      );
+      expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}')
+        .withInput({
+          none: 'No people',
+          people: []
+        })
+        .toCompileTo('No people');
     });
   });
 
   describe('standalone sections', function() {
     it('block standalone else sections', function() {
-      shouldCompileTo(
-        '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
-        { none: 'No people' },
-        'No people\n'
-      );
-      shouldCompileTo(
-        '{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n',
-        { none: 'No people' },
-        'No people\n'
-      );
-      shouldCompileTo(
-        '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
-        { none: 'No people' },
-        'No people\n'
-      );
+      expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
+        .withInput({ none: 'No people' })
+        .toCompileTo('No people\n');
+
+      expectTemplate('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n')
+        .withInput({ none: 'No people' })
+        .toCompileTo('No people\n');
+
+      expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
+        .withInput({ none: 'No people' })
+        .toCompileTo('No people\n');
     });
+
     it('block standalone else sections can be disabled', function() {
-      shouldCompileTo(
-        '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
-        [{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
-        '\nNo people\n\n'
-      );
-      shouldCompileTo(
-        '{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n',
-        [{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
-        '\nNo people\n\n'
-      );
+      expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
+        .withInput({ none: 'No people' })
+        .withCompileOptions({ ignoreStandalone: true })
+        .toCompileTo('\nNo people\n\n');
+
+      expectTemplate('{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n')
+        .withInput({ none: 'No people' })
+        .withCompileOptions({ ignoreStandalone: true })
+        .toCompileTo('\nNo people\n\n');
     });
+
     it('block standalone chained else sections', function() {
-      shouldCompileTo(
-        '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n',
-        { none: 'No people' },
-        'No people\n'
-      );
-      shouldCompileTo(
-        '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n',
-        { none: 'No people' },
-        'No people\n'
-      );
+      expectTemplate(
+        '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n'
+      )
+        .withInput({ none: 'No people' })
+        .toCompileTo('No people\n');
+
+      expectTemplate(
+        '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n'
+      )
+        .withInput({ none: 'No people' })
+        .toCompileTo('No people\n');
     });
+
     it('should handle nesting', function() {
-      shouldCompileTo(
-        '{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.',
-        { data: [1, 3, 5] },
-        '1\n3\n5\nOK.'
-      );
+      expectTemplate('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.')
+        .withInput({
+          data: [1, 3, 5]
+        })
+        .toCompileTo('1\n3\n5\nOK.');
     });
   });
 
   describe('compat mode', function() {
     it('block with deep recursive lookup lookup', function() {
-      var string =
-        '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}';
-      var hash = { omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] };
-
-      shouldCompileTo(
-        string,
-        [hash, undefined, undefined, true],
-        'Goodbye cruel OMG!'
-      );
+      expectTemplate(
+        '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}'
+      )
+        .withInput({ omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] })
+        .withCompileOptions({ compat: true })
+        .toCompileTo('Goodbye cruel OMG!');
     });
 
     it('block with deep recursive pathed lookup', function() {
-      var string =
-        '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
-      var hash = {
-        omg: { yes: 'OMG!' },
-        outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
-      };
-
-      shouldCompileTo(
-        string,
-        [hash, undefined, undefined, true],
-        'Goodbye cruel OMG!'
-      );
+      expectTemplate(
+        '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'
+      )
+        .withInput({
+          omg: { yes: 'OMG!' },
+          outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
+        })
+        .withCompileOptions({ compat: true })
+        .toCompileTo('Goodbye cruel OMG!');
     });
+
     it('block with missed recursive lookup', function() {
-      var string =
-        '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
-      var hash = {
-        omg: { no: 'OMG!' },
-        outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
-      };
-
-      shouldCompileTo(
-        string,
-        [hash, undefined, undefined, true],
-        'Goodbye cruel '
-      );
+      expectTemplate(
+        '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'
+      )
+        .withInput({
+          omg: { no: 'OMG!' },
+          outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
+        })
+        .withCompileOptions({ compat: true })
+        .toCompileTo('Goodbye cruel ');
     });
   });
 
   describe('decorators', function() {
     it('should apply mustache decorators', function() {
-      var helpers = {
-        helper: function(options) {
+      expectTemplate('{{#helper}}{{*decorator}}{{/helper}}')
+        .withHelper('helper', function(options) {
           return options.fn.run;
-        }
-      };
-      var decorators = {
-        decorator: function(fn) {
+        })
+        .withDecorator('decorator', function(fn) {
           fn.run = 'success';
           return fn;
-        }
-      };
-      shouldCompileTo(
-        '{{#helper}}{{*decorator}}{{/helper}}',
-        { hash: {}, helpers: helpers, decorators: decorators },
-        'success'
-      );
+        })
+        .toCompileTo('success');
     });
+
     it('should apply allow undefined return', function() {
-      var helpers = {
-        helper: function(options) {
+      expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}')
+        .withHelper('helper', function(options) {
           return options.fn() + options.fn.run;
-        }
-      };
-      var decorators = {
-        decorator: function(fn) {
+        })
+        .withDecorator('decorator', function(fn) {
           fn.run = 'cess';
-        }
-      };
-      shouldCompileTo(
-        '{{#helper}}{{*decorator}}suc{{/helper}}',
-        { hash: {}, helpers: helpers, decorators: decorators },
-        'success'
-      );
+        })
+        .toCompileTo('success');
     });
 
     it('should apply block decorators', function() {
-      var helpers = {
-        helper: function(options) {
+      expectTemplate(
+        '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}'
+      )
+        .withHelper('helper', function(options) {
           return options.fn.run;
-        }
-      };
-      var decorators = {
-        decorator: function(fn, props, container, options) {
+        })
+        .withDecorator('decorator', function(fn, props, container, options) {
           fn.run = options.fn();
           return fn;
-        }
-      };
-      shouldCompileTo(
-        '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
-        { hash: {}, helpers: helpers, decorators: decorators },
-        'success'
-      );
+        })
+        .toCompileTo('success');
     });
+
     it('should support nested decorators', function() {
-      var helpers = {
-        helper: function(options) {
+      expectTemplate(
+        '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}'
+      )
+        .withHelper('helper', function(options) {
           return options.fn.run;
-        }
-      };
-      var decorators = {
-        decorator: function(fn, props, container, options) {
-          fn.run = options.fn.nested + options.fn();
-          return fn;
-        },
-        nested: function(fn, props, container, options) {
-          props.nested = options.fn();
-        }
-      };
-      shouldCompileTo(
-        '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
-        { hash: {}, helpers: helpers, decorators: decorators },
-        'success'
-      );
+        })
+        .withDecorators({
+          decorator: function(fn, props, container, options) {
+            fn.run = options.fn.nested + options.fn();
+            return fn;
+          },
+          nested: function(fn, props, container, options) {
+            props.nested = options.fn();
+          }
+        })
+        .toCompileTo('success');
     });
 
     it('should apply multiple decorators', function() {
-      var helpers = {
-        helper: function(options) {
+      expectTemplate(
+        '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}'
+      )
+        .withHelper('helper', function(options) {
           return options.fn.run;
-        }
-      };
-      var decorators = {
-        decorator: function(fn, props, container, options) {
+        })
+        .withDecorator('decorator', function(fn, props, container, options) {
           fn.run = (fn.run || '') + options.fn();
           return fn;
-        }
-      };
-      shouldCompileTo(
-        '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
-        { hash: {}, helpers: helpers, decorators: decorators },
-        'success'
-      );
+        })
+        .toCompileTo('success');
     });
 
     it('should access parent variables', function() {
-      var helpers = {
-        helper: function(options) {
+      expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}')
+        .withHelper('helper', function(options) {
           return options.fn.run;
-        }
-      };
-      var decorators = {
-        decorator: function(fn, props, container, options) {
+        })
+        .withDecorator('decorator', function(fn, props, container, options) {
           fn.run = options.args;
           return fn;
-        }
-      };
-      shouldCompileTo(
-        '{{#helper}}{{*decorator foo}}{{/helper}}',
-        { hash: { foo: 'success' }, helpers: helpers, decorators: decorators },
-        'success'
-      );
+        })
+        .withInput({ foo: 'success' })
+        .toCompileTo('success');
     });
+
     it('should work with root program', function() {
       var run;
-      var decorators = {
-        decorator: function(fn, props, container, options) {
+      expectTemplate('{{*decorator "success"}}')
+        .withDecorator('decorator', function(fn, props, container, options) {
           equals(options.args[0], 'success');
           run = true;
           return fn;
-        }
-      };
-      shouldCompileTo(
-        '{{*decorator "success"}}',
-        { hash: { foo: 'success' }, decorators: decorators },
-        ''
-      );
+        })
+        .withInput({ foo: 'success' })
+        .toCompileTo('');
       equals(run, true);
     });
+
     it('should fail when accessing variables from root', function() {
       var run;
-      var decorators = {
-        decorator: function(fn, props, container, options) {
+      expectTemplate('{{*decorator foo}}')
+        .withDecorator('decorator', function(fn, props, container, options) {
           equals(options.args[0], undefined);
           run = true;
           return fn;
-        }
-      };
-      shouldCompileTo(
-        '{{*decorator foo}}',
-        { hash: { foo: 'fail' }, decorators: decorators },
-        ''
-      );
+        })
+        .withInput({ foo: 'fail' })
+        .toCompileTo('');
       equals(run, true);
     });
 
@@ -481,6 +431,7 @@ describe('blocks', function() {
         equals(handlebarsEnv.decorators.foo, undefined);
         equals(handlebarsEnv.decorators.bar, undefined);
       });
+
       it('fails with multiple and args', function() {
         shouldThrow(
           function() {
diff --git a/spec/builtins.js b/spec/builtins.js
index c74092e1..a43fb81f 100644
--- a/spec/builtins.js
+++ b/spec/builtins.js
@@ -2,157 +2,184 @@ describe('builtin helpers', function() {
   describe('#if', function() {
     it('if', function() {
       var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
-      shouldCompileTo(
-        string,
-        { goodbye: true, world: 'world' },
-        'GOODBYE cruel world!',
-        'if with boolean argument shows the contents when true'
-      );
-      shouldCompileTo(
-        string,
-        { goodbye: 'dummy', world: 'world' },
-        'GOODBYE cruel world!',
-        'if with string argument shows the contents'
-      );
-      shouldCompileTo(
-        string,
-        { goodbye: false, world: 'world' },
-        'cruel world!',
-        'if with boolean argument does not show the contents when false'
-      );
-      shouldCompileTo(
-        string,
-        { world: 'world' },
-        'cruel world!',
-        'if with undefined does not show the contents'
-      );
-      shouldCompileTo(
-        string,
-        { goodbye: ['foo'], world: 'world' },
-        'GOODBYE cruel world!',
-        'if with non-empty array shows the contents'
-      );
-      shouldCompileTo(
-        string,
-        { goodbye: [], world: 'world' },
-        'cruel world!',
-        'if with empty array does not show the contents'
-      );
-      shouldCompileTo(
-        string,
-        { goodbye: 0, world: 'world' },
-        'cruel world!',
-        'if with zero does not show the contents'
-      );
-      shouldCompileTo(
-        '{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!',
-        { goodbye: 0, world: 'world' },
-        'GOODBYE cruel world!',
-        'if with zero does not show the contents'
-      );
+
+      expectTemplate(string)
+        .withInput({
+          goodbye: true,
+          world: 'world'
+        })
+        .withMessage('if with boolean argument shows the contents when true')
+        .toCompileTo('GOODBYE cruel world!');
+
+      expectTemplate(string)
+        .withInput({
+          goodbye: 'dummy',
+          world: 'world'
+        })
+        .withMessage('if with string argument shows the contents')
+        .toCompileTo('GOODBYE cruel world!');
+
+      expectTemplate(string)
+        .withInput({
+          goodbye: false,
+          world: 'world'
+        })
+        .withMessage(
+          'if with boolean argument does not show the contents when false'
+        )
+        .toCompileTo('cruel world!');
+
+      expectTemplate(string)
+        .withInput({ world: 'world' })
+        .withMessage('if with undefined does not show the contents')
+        .toCompileTo('cruel world!');
+
+      expectTemplate(string)
+        .withInput({
+          goodbye: ['foo'],
+          world: 'world'
+        })
+        .withMessage('if with non-empty array shows the contents')
+        .toCompileTo('GOODBYE cruel world!');
+
+      expectTemplate(string)
+        .withInput({
+          goodbye: [],
+          world: 'world'
+        })
+        .withMessage('if with empty array does not show the contents')
+        .toCompileTo('cruel world!');
+
+      expectTemplate(string)
+        .withInput({
+          goodbye: 0,
+          world: 'world'
+        })
+        .withMessage('if with zero does not show the contents')
+        .toCompileTo('cruel world!');
+
+      expectTemplate(
+        '{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!'
+      )
+        .withInput({
+          goodbye: 0,
+          world: 'world'
+        })
+        .withMessage('if with zero does not show the contents')
+        .toCompileTo('GOODBYE cruel world!');
     });
 
     it('if with function argument', function() {
       var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
-      shouldCompileTo(
-        string,
-        {
+
+      expectTemplate(string)
+        .withInput({
           goodbye: function() {
             return true;
           },
           world: 'world'
-        },
-        'GOODBYE cruel world!',
-        'if with function shows the contents when function returns true'
-      );
-      shouldCompileTo(
-        string,
-        {
+        })
+        .withMessage(
+          'if with function shows the contents when function returns true'
+        )
+        .toCompileTo('GOODBYE cruel world!');
+
+      expectTemplate(string)
+        .withInput({
           goodbye: function() {
             return this.world;
           },
           world: 'world'
-        },
-        'GOODBYE cruel world!',
-        'if with function shows the contents when function returns string'
-      );
-      shouldCompileTo(
-        string,
-        {
+        })
+        .withMessage(
+          'if with function shows the contents when function returns string'
+        )
+        .toCompileTo('GOODBYE cruel world!');
+
+      expectTemplate(string)
+        .withInput({
           goodbye: function() {
             return false;
           },
           world: 'world'
-        },
-        'cruel world!',
-        'if with function does not show the contents when returns false'
-      );
-      shouldCompileTo(
-        string,
-        {
+        })
+        .withMessage(
+          'if with function does not show the contents when returns false'
+        )
+        .toCompileTo('cruel world!');
+
+      expectTemplate(string)
+        .withInput({
           goodbye: function() {
             return this.foo;
           },
           world: 'world'
-        },
-        'cruel world!',
-        'if with function does not show the contents when returns undefined'
-      );
+        })
+        .withMessage(
+          'if with function does not show the contents when returns undefined'
+        )
+        .toCompileTo('cruel world!');
     });
 
     it('should not change the depth list', function() {
-      var string =
-        '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}';
-      shouldCompileTo(
-        string,
-        { foo: { goodbye: true }, world: 'world' },
-        'GOODBYE cruel world!'
-      );
+      expectTemplate(
+        '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}'
+      )
+        .withInput({
+          foo: { goodbye: true },
+          world: 'world'
+        })
+        .toCompileTo('GOODBYE cruel world!');
     });
   });
 
   describe('#with', function() {
     it('with', function() {
-      var string = '{{#with person}}{{first}} {{last}}{{/with}}';
-      shouldCompileTo(
-        string,
-        { person: { first: 'Alan', last: 'Johnson' } },
-        'Alan Johnson'
-      );
+      expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}')
+        .withInput({
+          person: {
+            first: 'Alan',
+            last: 'Johnson'
+          }
+        })
+        .toCompileTo('Alan Johnson');
     });
+
     it('with with function argument', function() {
-      var string = '{{#with person}}{{first}} {{last}}{{/with}}';
-      shouldCompileTo(
-        string,
-        {
+      expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}')
+        .withInput({
           person: function() {
-            return { first: 'Alan', last: 'Johnson' };
+            return {
+              first: 'Alan',
+              last: 'Johnson'
+            };
           }
-        },
-        'Alan Johnson'
-      );
+        })
+        .toCompileTo('Alan Johnson');
     });
+
     it('with with else', function() {
-      var string =
-        '{{#with person}}Person is present{{else}}Person is not present{{/with}}';
-      shouldCompileTo(string, {}, 'Person is not present');
+      expectTemplate(
+        '{{#with person}}Person is present{{else}}Person is not present{{/with}}'
+      ).toCompileTo('Person is not present');
     });
+
     it('with provides block parameter', function() {
-      var string = '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}';
-      shouldCompileTo(
-        string,
-        { person: { first: 'Alan', last: 'Johnson' } },
-        'Alan Johnson'
-      );
+      expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}')
+        .withInput({
+          person: {
+            first: 'Alan',
+            last: 'Johnson'
+          }
+        })
+        .toCompileTo('Alan Johnson');
     });
-    it('works when data is disabled', function() {
-      var template = CompilerContext.compile(
-        '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}',
-        { data: false }
-      );
 
-      var result = template({ person: { first: 'Alan', last: 'Johnson' } });
-      equals(result, 'Alan Johnson');
+    it('works when data is disabled', function() {
+      expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}')
+        .withInput({ person: { first: 'Alan', last: 'Johnson' } })
+        .withCompileOptions({ data: false })
+        .toCompileTo('Alan Johnson');
     });
   });
 
@@ -165,55 +192,55 @@ describe('builtin helpers', function() {
 
     it('each', function() {
       var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
-      var hash = {
-        goodbyes: [
-          { text: 'goodbye' },
-          { text: 'Goodbye' },
-          { text: 'GOODBYE' }
-        ],
-        world: 'world'
-      };
-      shouldCompileTo(
-        string,
-        hash,
-        'goodbye! Goodbye! GOODBYE! cruel world!',
-        'each with array argument iterates over the contents when not empty'
-      );
-      shouldCompileTo(
-        string,
-        { goodbyes: [], world: 'world' },
-        'cruel world!',
-        'each with array argument ignores the contents when empty'
-      );
+
+      expectTemplate(string)
+        .withInput({
+          goodbyes: [
+            { text: 'goodbye' },
+            { text: 'Goodbye' },
+            { text: 'GOODBYE' }
+          ],
+          world: 'world'
+        })
+        .withMessage(
+          'each with array argument iterates over the contents when not empty'
+        )
+        .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
+
+      expectTemplate(string)
+        .withInput({
+          goodbyes: [],
+          world: 'world'
+        })
+        .withMessage('each with array argument ignores the contents when empty')
+        .toCompileTo('cruel world!');
     });
 
     it('each without data', function() {
-      var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
-      var hash = {
-        goodbyes: [
-          { text: 'goodbye' },
-          { text: 'Goodbye' },
-          { text: 'GOODBYE' }
-        ],
-        world: 'world'
-      };
-      shouldCompileTo(
-        string,
-        [hash, , , , false],
-        'goodbye! Goodbye! GOODBYE! cruel world!'
-      );
+      expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!')
+        .withInput({
+          goodbyes: [
+            { text: 'goodbye' },
+            { text: 'Goodbye' },
+            { text: 'GOODBYE' }
+          ],
+          world: 'world'
+        })
+        .withRuntimeOptions({ data: false })
+        .withCompileOptions({ data: false })
+        .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
 
-      hash = { goodbyes: 'cruel', world: 'world' };
-      shouldCompileTo(
-        '{{#each .}}{{.}}{{/each}}',
-        [hash, , , , false],
-        'cruelworld'
-      );
+      expectTemplate('{{#each .}}{{.}}{{/each}}')
+        .withInput({ goodbyes: 'cruel', world: 'world' })
+        .withRuntimeOptions({ data: false })
+        .withCompileOptions({ data: false })
+        .toCompileTo('cruelworld');
     });
 
     it('each without context', function() {
-      var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
-      shouldCompileTo(string, [, , , ,], 'cruel !');
+      expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!')
+        .withInput(undefined)
+        .toCompileTo('cruel !');
     });
 
     it('each with an object and @key', function() {
@@ -241,269 +268,232 @@ describe('builtin helpers', function() {
         true,
         'each with object argument iterates over the contents when not empty'
       );
-      shouldCompileTo(string, { goodbyes: {}, world: 'world' }, 'cruel world!');
+
+      expectTemplate(string)
+        .withInput({
+          goodbyes: {},
+          world: 'world'
+        })
+        .toCompileTo('cruel world!');
     });
 
     it('each with @index', function() {
-      var string =
-        '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
-      var hash = {
-        goodbyes: [
-          { text: 'goodbye' },
-          { text: 'Goodbye' },
-          { text: 'GOODBYE' }
-        ],
-        world: 'world'
-      };
-
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
-
-      equal(
-        result,
-        '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
-        'The @index variable is used'
-      );
+      expectTemplate(
+        '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
+      )
+        .withInput({
+          goodbyes: [
+            { text: 'goodbye' },
+            { text: 'Goodbye' },
+            { text: 'GOODBYE' }
+          ],
+          world: 'world'
+        })
+        .withMessage('The @index variable is used')
+        .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
     });
 
     it('each with nested @index', function() {
-      var string =
-        '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!';
-      var hash = {
-        goodbyes: [
-          { text: 'goodbye' },
-          { text: 'Goodbye' },
-          { text: 'GOODBYE' }
-        ],
-        world: 'world'
-      };
-
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
-
-      equal(
-        result,
-        '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!',
-        'The @index variable is used'
-      );
+      expectTemplate(
+        '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!'
+      )
+        .withInput({
+          goodbyes: [
+            { text: 'goodbye' },
+            { text: 'Goodbye' },
+            { text: 'GOODBYE' }
+          ],
+          world: 'world'
+        })
+        .withMessage('The @index variable is used')
+        .toCompileTo(
+          '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!'
+        );
     });
 
     it('each with block params', function() {
-      var string =
-        '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!';
-      var hash = {
-        goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }],
-        world: 'world'
-      };
-
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
-
-      equal(
-        result,
-        '0. goodbye!  0 0 0 1 After 0 1. Goodbye!  1 0 1 1 After 1 cruel world!'
-      );
+      expectTemplate(
+        '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!'
+      )
+        .withInput({
+          goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }],
+          world: 'world'
+        })
+        .toCompileTo(
+          '0. goodbye!  0 0 0 1 After 0 1. Goodbye!  1 0 1 1 After 1 cruel world!'
+        );
     });
 
     it('each object with @index', function() {
-      var string =
-        '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
-      var hash = {
-        goodbyes: {
-          a: { text: 'goodbye' },
-          b: { text: 'Goodbye' },
-          c: { text: 'GOODBYE' }
-        },
-        world: 'world'
-      };
-
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
-
-      equal(
-        result,
-        '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
-        'The @index variable is used'
-      );
+      expectTemplate(
+        '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
+      )
+        .withInput({
+          goodbyes: {
+            a: { text: 'goodbye' },
+            b: { text: 'Goodbye' },
+            c: { text: 'GOODBYE' }
+          },
+          world: 'world'
+        })
+        .withMessage('The @index variable is used')
+        .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
     });
 
     it('each with @first', function() {
-      var string =
-        '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
-      var hash = {
-        goodbyes: [
-          { text: 'goodbye' },
-          { text: 'Goodbye' },
-          { text: 'GOODBYE' }
-        ],
-        world: 'world'
-      };
-
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
-
-      equal(result, 'goodbye! cruel world!', 'The @first variable is used');
+      expectTemplate(
+        '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
+      )
+        .withInput({
+          goodbyes: [
+            { text: 'goodbye' },
+            { text: 'Goodbye' },
+            { text: 'GOODBYE' }
+          ],
+          world: 'world'
+        })
+        .withMessage('The @first variable is used')
+        .toCompileTo('goodbye! cruel world!');
     });
 
     it('each with nested @first', function() {
-      var string =
-        '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
-      var hash = {
-        goodbyes: [
-          { text: 'goodbye' },
-          { text: 'Goodbye' },
-          { text: 'GOODBYE' }
-        ],
-        world: 'world'
-      };
-
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
-
-      equal(
-        result,
-        '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!',
-        'The @first variable is used'
-      );
+      expectTemplate(
+        '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'
+      )
+        .withInput({
+          goodbyes: [
+            { text: 'goodbye' },
+            { text: 'Goodbye' },
+            { text: 'GOODBYE' }
+          ],
+          world: 'world'
+        })
+        .withMessage('The @first variable is used')
+        .toCompileTo(
+          '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!'
+        );
     });
 
     it('each object with @first', function() {
-      var string =
-        '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
-      var hash = {
-        goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
-        world: 'world'
-      };
-
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
-
-      equal(result, 'goodbye! cruel world!', 'The @first variable is used');
+      expectTemplate(
+        '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
+      )
+        .withInput({
+          goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
+          world: 'world'
+        })
+        .withMessage('The @first variable is used')
+        .toCompileTo('goodbye! cruel world!');
     });
 
     it('each with @last', function() {
-      var string =
-        '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
-      var hash = {
-        goodbyes: [
-          { text: 'goodbye' },
-          { text: 'Goodbye' },
-          { text: 'GOODBYE' }
-        ],
-        world: 'world'
-      };
-
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
-
-      equal(result, 'GOODBYE! cruel world!', 'The @last variable is used');
+      expectTemplate(
+        '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
+      )
+        .withInput({
+          goodbyes: [
+            { text: 'goodbye' },
+            { text: 'Goodbye' },
+            { text: 'GOODBYE' }
+          ],
+          world: 'world'
+        })
+        .withMessage('The @last variable is used')
+        .toCompileTo('GOODBYE! cruel world!');
     });
 
     it('each object with @last', function() {
-      var string =
-        '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
-      var hash = {
-        goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
-        world: 'world'
-      };
-
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
-
-      equal(result, 'Goodbye! cruel world!', 'The @last variable is used');
+      expectTemplate(
+        '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
+      )
+        .withInput({
+          goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
+          world: 'world'
+        })
+        .withMessage('The @last variable is used')
+        .toCompileTo('Goodbye! cruel world!');
     });
 
     it('each with nested @last', function() {
-      var string =
-        '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
-      var hash = {
-        goodbyes: [
-          { text: 'goodbye' },
-          { text: 'Goodbye' },
-          { text: 'GOODBYE' }
-        ],
-        world: 'world'
-      };
-
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
-
-      equal(
-        result,
-        '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!',
-        'The @last variable is used'
-      );
-    });
-
-    it('each with function argument', function() {
-      var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
-      var hash = {
-        goodbyes: function() {
-          return [
+      expectTemplate(
+        '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'
+      )
+        .withInput({
+          goodbyes: [
             { text: 'goodbye' },
             { text: 'Goodbye' },
             { text: 'GOODBYE' }
-          ];
-        },
-        world: 'world'
-      };
-      shouldCompileTo(
-        string,
-        hash,
-        'goodbye! Goodbye! GOODBYE! cruel world!',
-        'each with array function argument iterates over the contents when not empty'
-      );
-      shouldCompileTo(
-        string,
-        { goodbyes: [], world: 'world' },
-        'cruel world!',
-        'each with array function argument ignores the contents when empty'
-      );
+          ],
+          world: 'world'
+        })
+        .withMessage('The @last variable is used')
+        .toCompileTo(
+          '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!'
+        );
     });
 
-    it('each object when last key is an empty string', function() {
-      var string =
-        '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
-      var hash = {
-        goodbyes: {
-          a: { text: 'goodbye' },
-          b: { text: 'Goodbye' },
-          '': { text: 'GOODBYE' }
-        },
-        world: 'world'
-      };
+    it('each with function argument', function() {
+      var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
 
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
+      expectTemplate(string)
+        .withInput({
+          goodbyes: function() {
+            return [
+              { text: 'goodbye' },
+              { text: 'Goodbye' },
+              { text: 'GOODBYE' }
+            ];
+          },
+          world: 'world'
+        })
+        .withMessage(
+          'each with array function argument iterates over the contents when not empty'
+        )
+        .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
 
-      equal(
-        result,
-        '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
-        'Empty string key is not skipped'
-      );
+      expectTemplate(string)
+        .withInput({
+          goodbyes: [],
+          world: 'world'
+        })
+        .withMessage(
+          'each with array function argument ignores the contents when empty'
+        )
+        .toCompileTo('cruel world!');
     });
 
-    it('data passed to helpers', function() {
-      var string = '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}';
-      var hash = { letters: ['a', 'b', 'c'] };
+    it('each object when last key is an empty string', function() {
+      expectTemplate(
+        '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
+      )
+        .withInput({
+          goodbyes: {
+            a: { text: 'goodbye' },
+            b: { text: 'Goodbye' },
+            '': { text: 'GOODBYE' }
+          },
+          world: 'world'
+        })
+        .withMessage('Empty string key is not skipped')
+        .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
+    });
 
-      var template = CompilerContext.compile(string);
-      var result = template(hash, {
-        data: {
-          exclaim: '!'
-        }
-      });
-      equal(result, 'a!b!c!', 'should output data');
+    it('data passed to helpers', function() {
+      expectTemplate(
+        '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}'
+      )
+        .withInput({ letters: ['a', 'b', 'c'] })
+        .withMessage('should output data')
+        .withRuntimeOptions({
+          data: {
+            exclaim: '!'
+          }
+        })
+        .toCompileTo('a!b!c!');
     });
 
     it('each on implicit context', function() {
-      shouldThrow(
-        function() {
-          var template = CompilerContext.compile(
-            '{{#each}}{{text}}! {{/each}}cruel world!'
-          );
-          template({});
-        },
+      expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow(
         handlebarsEnv.Exception,
         'Must pass iterator to #each'
       );
@@ -530,25 +520,30 @@ describe('builtin helpers', function() {
           return new Iterator(this.arr);
         };
         var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
-        var goodbyes = new Iterable([
-          { text: 'goodbye' },
-          { text: 'Goodbye' },
-          { text: 'GOODBYE' }
-        ]);
-        var goodbyesEmpty = new Iterable([]);
-        var hash = { goodbyes: goodbyes, world: 'world' };
-        shouldCompileTo(
-          string,
-          hash,
-          'goodbye! Goodbye! GOODBYE! cruel world!',
-          'each with array argument iterates over the contents when not empty'
-        );
-        shouldCompileTo(
-          string,
-          { goodbyes: goodbyesEmpty, world: 'world' },
-          'cruel world!',
-          'each with array argument ignores the contents when empty'
-        );
+
+        expectTemplate(string)
+          .withInput({
+            goodbyes: new Iterable([
+              { text: 'goodbye' },
+              { text: 'Goodbye' },
+              { text: 'GOODBYE' }
+            ]),
+            world: 'world'
+          })
+          .withMessage(
+            'each with array argument iterates over the contents when not empty'
+          )
+          .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
+
+        expectTemplate(string)
+          .withInput({
+            goodbyes: new Iterable([]),
+            world: 'world'
+          })
+          .withMessage(
+            'each with array argument ignores the contents when empty'
+          )
+          .toCompileTo('cruel world!');
       });
     }
   });
@@ -572,36 +567,37 @@ describe('builtin helpers', function() {
     });
 
     it('should call logger at default level', function() {
-      var string = '{{log blah}}';
-      var hash = { blah: 'whee' };
-
       var levelArg, logArg;
       handlebarsEnv.log = function(level, arg) {
         levelArg = level;
         logArg = arg;
       };
 
-      shouldCompileTo(string, hash, '', 'log should not display');
+      expectTemplate('{{log blah}}')
+        .withInput({ blah: 'whee' })
+        .withMessage('log should not display')
+        .toCompileTo('');
       equals(1, levelArg, 'should call log with 1');
       equals('whee', logArg, "should call log with 'whee'");
     });
-    it('should call logger at data level', function() {
-      var string = '{{log blah}}';
-      var hash = { blah: 'whee' };
 
+    it('should call logger at data level', function() {
       var levelArg, logArg;
       handlebarsEnv.log = function(level, arg) {
         levelArg = level;
         logArg = arg;
       };
 
-      shouldCompileTo(string, [hash, , , , { level: '03' }], '');
+      expectTemplate('{{log blah}}')
+        .withInput({ blah: 'whee' })
+        .withRuntimeOptions({ data: { level: '03' } })
+        .withCompileOptions({ data: true })
+        .toCompileTo('');
       equals('03', levelArg);
       equals('whee', logArg);
     });
+
     it('should output to info', function() {
-      var string = '{{log blah}}';
-      var hash = { blah: 'whee' };
       var called;
 
       console.info = function(info) {
@@ -617,12 +613,13 @@ describe('builtin helpers', function() {
         console.log = $log;
       };
 
-      shouldCompileTo(string, hash, '');
+      expectTemplate('{{log blah}}')
+        .withInput({ blah: 'whee' })
+        .toCompileTo('');
       equals(true, called);
     });
+
     it('should log at data level', function() {
-      var string = '{{log blah}}';
-      var hash = { blah: 'whee' };
       var called;
 
       console.error = function(log) {
@@ -631,13 +628,16 @@ describe('builtin helpers', function() {
         console.error = $error;
       };
 
-      shouldCompileTo(string, [hash, , , , { level: '03' }], '');
+      expectTemplate('{{log blah}}')
+        .withInput({ blah: 'whee' })
+        .withRuntimeOptions({ data: { level: '03' } })
+        .withCompileOptions({ data: true })
+        .toCompileTo('');
       equals(true, called);
     });
+
     it('should handle missing logger', function() {
-      var string = '{{log blah}}';
-      var hash = { blah: 'whee' },
-        called = false;
+      var called = false;
 
       console.error = undefined;
       console.log = function(log) {
@@ -646,13 +646,15 @@ describe('builtin helpers', function() {
         console.log = $log;
       };
 
-      shouldCompileTo(string, [hash, , , , { level: '03' }], '');
+      expectTemplate('{{log blah}}')
+        .withInput({ blah: 'whee' })
+        .withRuntimeOptions({ data: { level: '03' } })
+        .withCompileOptions({ data: true })
+        .toCompileTo('');
       equals(true, called);
     });
 
     it('should handle string log levels', function() {
-      var string = '{{log blah}}';
-      var hash = { blah: 'whee' };
       var called;
 
       console.error = function(log) {
@@ -660,17 +662,24 @@ describe('builtin helpers', function() {
         called = true;
       };
 
-      shouldCompileTo(string, [hash, , , , { level: 'error' }], '');
+      expectTemplate('{{log blah}}')
+        .withInput({ blah: 'whee' })
+        .withRuntimeOptions({ data: { level: 'error' } })
+        .withCompileOptions({ data: true })
+        .toCompileTo('');
       equals(true, called);
 
       called = false;
 
-      shouldCompileTo(string, [hash, , , , { level: 'ERROR' }], '');
+      expectTemplate('{{log blah}}')
+        .withInput({ blah: 'whee' })
+        .withRuntimeOptions({ data: { level: 'ERROR' } })
+        .withCompileOptions({ data: true })
+        .toCompileTo('');
       equals(true, called);
     });
+
     it('should handle hash log levels', function() {
-      var string = '{{log blah level="error"}}';
-      var hash = { blah: 'whee' };
       var called;
 
       console.error = function(log) {
@@ -678,12 +687,13 @@ describe('builtin helpers', function() {
         called = true;
       };
 
-      shouldCompileTo(string, hash, '');
+      expectTemplate('{{log blah level="error"}}')
+        .withInput({ blah: 'whee' })
+        .toCompileTo('');
       equals(true, called);
     });
+
     it('should handle hash log levels', function() {
-      var string = '{{log blah level="debug"}}';
-      var hash = { blah: 'whee' };
       var called = false;
 
       console.info = console.log = console.error = console.debug = function() {
@@ -691,12 +701,13 @@ describe('builtin helpers', function() {
         console.info = console.log = console.error = console.debug = $log;
       };
 
-      shouldCompileTo(string, hash, '');
+      expectTemplate('{{log blah level="debug"}}')
+        .withInput({ blah: 'whee' })
+        .toCompileTo('');
       equals(false, called);
     });
+
     it('should pass multiple log arguments', function() {
-      var string = '{{log blah "foo" 1}}';
-      var hash = { blah: 'whee' };
       var called;
 
       console.info = console.log = function(log1, log2, log3) {
@@ -707,13 +718,13 @@ describe('builtin helpers', function() {
         console.log = $log;
       };
 
-      shouldCompileTo(string, hash, '');
+      expectTemplate('{{log blah "foo" 1}}')
+        .withInput({ blah: 'whee' })
+        .toCompileTo('');
       equals(true, called);
     });
 
     it('should pass zero log arguments', function() {
-      var string = '{{log}}';
-      var hash = { blah: 'whee' };
       var called;
 
       console.info = console.log = function() {
@@ -722,8 +733,8 @@ describe('builtin helpers', function() {
         console.log = $log;
       };
 
-      expectTemplate(string)
-        .withInput(hash)
+      expectTemplate('{{log}}')
+        .withInput({ blah: 'whee' })
         .toCompileTo('');
       expect(called).to.be.true();
     });
@@ -732,22 +743,15 @@ describe('builtin helpers', function() {
 
   describe('#lookup', function() {
     it('should lookup arbitrary content', function() {
-      var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}',
-        hash = { goodbyes: [0, 1], data: ['foo', 'bar'] };
-
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
-
-      equal(result, 'foobar');
+      expectTemplate('{{#each goodbyes}}{{lookup ../data .}}{{/each}}')
+        .withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] })
+        .toCompileTo('foobar');
     });
-    it('should not fail on undefined value', function() {
-      var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}',
-        hash = { goodbyes: [0, 1], data: ['foo', 'bar'] };
 
-      var template = CompilerContext.compile(string);
-      var result = template(hash);
-
-      equal(result, '');
+    it('should not fail on undefined value', function() {
+      expectTemplate('{{#each goodbyes}}{{lookup ../bar .}}{{/each}}')
+        .withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] })
+        .toCompileTo('');
     });
   });
 });
diff --git a/spec/data.js b/spec/data.js
index 0defdc14..bde61732 100644
--- a/spec/data.js
+++ b/spec/data.js
@@ -1,31 +1,24 @@
 describe('data', function() {
   it('passing in data to a compiled function that expects data - works with helpers', function() {
-    var template = CompilerContext.compile('{{hello}}', { data: true });
-
-    var helpers = {
-      hello: function(options) {
+    expectTemplate('{{hello}}')
+      .withCompileOptions({ data: true })
+      .withHelper('hello', function(options) {
         return options.data.adjective + ' ' + this.noun;
-      }
-    };
-
-    var result = template(
-      { noun: 'cat' },
-      { helpers: helpers, data: { adjective: 'happy' } }
-    );
-    equals('happy cat', result, 'Data output by helper');
+      })
+      .withRuntimeOptions({ data: { adjective: 'happy' } })
+      .withInput({ noun: 'cat' })
+      .withMessage('Data output by helper')
+      .toCompileTo('happy cat');
   });
 
   it('data can be looked up via @foo', function() {
-    var template = CompilerContext.compile('{{@hello}}');
-    var result = template({}, { data: { hello: 'hello' } });
-    equals('hello', result, '@foo retrieves template data');
+    expectTemplate('{{@hello}}')
+      .withRuntimeOptions({ data: { hello: 'hello' } })
+      .withMessage('@foo retrieves template data')
+      .toCompileTo('hello');
   });
 
   it('deep @foo triggers automatic top-level data', function() {
-    var template = CompilerContext.compile(
-      '{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'
-    );
-
     var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
 
     helpers.let = function(options) {
@@ -39,124 +32,92 @@ describe('data', function() {
       return options.fn(this, { data: frame });
     };
 
-    var result = template({ foo: true }, { helpers: helpers });
-    equals('Hello world', result, 'Automatic data was triggered');
+    expectTemplate(
+      '{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'
+    )
+      .withInput({ foo: true })
+      .withHelpers(helpers)
+      .withMessage('Automatic data was triggered')
+      .toCompileTo('Hello world');
   });
 
   it('parameter data can be looked up via @foo', function() {
-    var template = CompilerContext.compile('{{hello @world}}');
-    var helpers = {
-      hello: function(noun) {
+    expectTemplate('{{hello @world}}')
+      .withRuntimeOptions({ data: { world: 'world' } })
+      .withHelper('hello', function(noun) {
         return 'Hello ' + noun;
-      }
-    };
-
-    var result = template({}, { helpers: helpers, data: { world: 'world' } });
-    equals(
-      'Hello world',
-      result,
-      '@foo as a parameter retrieves template data'
-    );
+      })
+      .withMessage('@foo as a parameter retrieves template data')
+      .toCompileTo('Hello world');
   });
 
   it('hash values can be looked up via @foo', function() {
-    var template = CompilerContext.compile('{{hello noun=@world}}');
-    var helpers = {
-      hello: function(options) {
+    expectTemplate('{{hello noun=@world}}')
+      .withRuntimeOptions({ data: { world: 'world' } })
+      .withHelper('hello', function(options) {
         return 'Hello ' + options.hash.noun;
-      }
-    };
-
-    var result = template({}, { helpers: helpers, data: { world: 'world' } });
-    equals(
-      'Hello world',
-      result,
-      '@foo as a parameter retrieves template data'
-    );
+      })
+      .withMessage('@foo as a parameter retrieves template data')
+      .toCompileTo('Hello world');
   });
 
   it('nested parameter data can be looked up via @foo.bar', function() {
-    var template = CompilerContext.compile('{{hello @world.bar}}');
-    var helpers = {
-      hello: function(noun) {
+    expectTemplate('{{hello @world.bar}}')
+      .withRuntimeOptions({ data: { world: { bar: 'world' } } })
+      .withHelper('hello', function(noun) {
         return 'Hello ' + noun;
-      }
-    };
-
-    var result = template(
-      {},
-      { helpers: helpers, data: { world: { bar: 'world' } } }
-    );
-    equals(
-      'Hello world',
-      result,
-      '@foo as a parameter retrieves template data'
-    );
+      })
+      .withMessage('@foo as a parameter retrieves template data')
+      .toCompileTo('Hello world');
   });
 
   it('nested parameter data does not fail with @world.bar', function() {
-    var template = CompilerContext.compile('{{hello @world.bar}}');
-    var helpers = {
-      hello: function(noun) {
+    expectTemplate('{{hello @world.bar}}')
+      .withRuntimeOptions({ data: { foo: { bar: 'world' } } })
+      .withHelper('hello', function(noun) {
         return 'Hello ' + noun;
-      }
-    };
-
-    var result = template(
-      {},
-      { helpers: helpers, data: { foo: { bar: 'world' } } }
-    );
-    equals(
-      'Hello undefined',
-      result,
-      '@foo as a parameter retrieves template data'
-    );
+      })
+      .withMessage('@foo as a parameter retrieves template data')
+      .toCompileTo('Hello undefined');
   });
 
   it('parameter data throws when using complex scope references', function() {
-    var string = '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}';
-
-    shouldThrow(function() {
-      CompilerContext.compile(string);
-    }, Error);
+    expectTemplate(
+      '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}'
+    ).toThrow(Error);
   });
 
   it('data can be functions', function() {
-    var template = CompilerContext.compile('{{@hello}}');
-    var result = template(
-      {},
-      {
+    expectTemplate('{{@hello}}')
+      .withRuntimeOptions({
         data: {
           hello: function() {
             return 'hello';
           }
         }
-      }
-    );
-    equals('hello', result);
+      })
+      .toCompileTo('hello');
   });
+
   it('data can be functions with params', function() {
-    var template = CompilerContext.compile('{{@hello "hello"}}');
-    var result = template(
-      {},
-      {
+    expectTemplate('{{@hello "hello"}}')
+      .withRuntimeOptions({
         data: {
           hello: function(arg) {
             return arg;
           }
         }
-      }
-    );
-    equals('hello', result);
+      })
+      .toCompileTo('hello');
   });
 
   it('data is inherited downstream', function() {
-    var template = CompilerContext.compile(
-      '{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}',
-      { data: true }
-    );
-    var helpers = {
-      let: function(options) {
+    expectTemplate(
+      '{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}'
+    )
+      .withInput({ bar: { baz: 'hello world' } })
+      .withCompileOptions({ data: true })
+      .withHelper('let', function(options) {
         var frame = Handlebars.createFrame(options.data);
         for (var prop in options.hash) {
           if (prop in options.hash) {
@@ -164,201 +125,154 @@ describe('data', function() {
           }
         }
         return options.fn(this, { data: frame });
-      }
-    };
-
-    var result = template(
-      { bar: { baz: 'hello world' } },
-      { helpers: helpers, data: {} }
-    );
-    equals('2hello world1', result, 'data variables are inherited downstream');
+      })
+      .withRuntimeOptions({ data: {} })
+      .withMessage('data variables are inherited downstream')
+      .toCompileTo('2hello world1');
   });
 
   it('passing in data to a compiled function that expects data - works with helpers in partials', function() {
-    var template = CompilerContext.compile('{{>myPartial}}', { data: true });
-
-    var partials = {
-      myPartial: CompilerContext.compile('{{hello}}', { data: true })
-    };
-
-    var helpers = {
-      hello: function(options) {
+    expectTemplate('{{>myPartial}}')
+      .withCompileOptions({ data: true })
+      .withPartial('myPartial', '{{hello}}')
+      .withHelper('hello', function(options) {
         return options.data.adjective + ' ' + this.noun;
-      }
-    };
-
-    var result = template(
-      { noun: 'cat' },
-      { helpers: helpers, partials: partials, data: { adjective: 'happy' } }
-    );
-    equals('happy cat', result, 'Data output by helper inside partial');
+      })
+      .withInput({ noun: 'cat' })
+      .withRuntimeOptions({ data: { adjective: 'happy' } })
+      .withMessage('Data output by helper inside partial')
+      .toCompileTo('happy cat');
   });
 
   it('passing in data to a compiled function that expects data - works with helpers and parameters', function() {
-    var template = CompilerContext.compile('{{hello world}}', { data: true });
-
-    var helpers = {
-      hello: function(noun, options) {
+    expectTemplate('{{hello world}}')
+      .withCompileOptions({ data: true })
+      .withHelper('hello', function(noun, options) {
         return options.data.adjective + ' ' + noun + (this.exclaim ? '!' : '');
-      }
-    };
-
-    var result = template(
-      { exclaim: true, world: 'world' },
-      { helpers: helpers, data: { adjective: 'happy' } }
-    );
-    equals('happy world!', result, 'Data output by helper');
+      })
+      .withInput({ exclaim: true, world: 'world' })
+      .withRuntimeOptions({ data: { adjective: 'happy' } })
+      .withMessage('Data output by helper')
+      .toCompileTo('happy world!');
   });
 
   it('passing in data to a compiled function that expects data - works with block helpers', function() {
-    var template = CompilerContext.compile('{{#hello}}{{world}}{{/hello}}', {
-      data: true
-    });
-
-    var helpers = {
-      hello: function(options) {
+    expectTemplate('{{#hello}}{{world}}{{/hello}}')
+      .withCompileOptions({
+        data: true
+      })
+      .withHelper('hello', function(options) {
         return options.fn(this);
-      },
-      world: function(options) {
+      })
+      .withHelper('world', function(options) {
         return options.data.adjective + ' world' + (this.exclaim ? '!' : '');
-      }
-    };
-
-    var result = template(
-      { exclaim: true },
-      { helpers: helpers, data: { adjective: 'happy' } }
-    );
-    equals('happy world!', result, 'Data output by helper');
+      })
+      .withInput({ exclaim: true })
+      .withRuntimeOptions({ data: { adjective: 'happy' } })
+      .withMessage('Data output by helper')
+      .toCompileTo('happy world!');
   });
 
   it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() {
-    var template = CompilerContext.compile(
-      '{{#hello}}{{world ../zomg}}{{/hello}}',
-      { data: true }
-    );
-
-    var helpers = {
-      hello: function(options) {
+    expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
+      .withCompileOptions({ data: true })
+      .withHelper('hello', function(options) {
         return options.fn({ exclaim: '?' });
-      },
-      world: function(thing, options) {
+      })
+      .withHelper('world', function(thing, options) {
         return options.data.adjective + ' ' + thing + (this.exclaim || '');
-      }
-    };
-
-    var result = template(
-      { exclaim: true, zomg: 'world' },
-      { helpers: helpers, data: { adjective: 'happy' } }
-    );
-    equals('happy world?', result, 'Data output by helper');
+      })
+      .withInput({ exclaim: true, zomg: 'world' })
+      .withRuntimeOptions({ data: { adjective: 'happy' } })
+      .withMessage('Data output by helper')
+      .toCompileTo('happy world?');
   });
 
   it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() {
-    var template = CompilerContext.compile(
-      '{{#hello}}{{world ../zomg}}{{/hello}}',
-      { data: true }
-    );
-
-    var helpers = {
-      hello: function(options) {
+    expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
+      .withCompileOptions({ data: true })
+      .withHelper('hello', function(options) {
         return options.data.accessData + ' ' + options.fn({ exclaim: '?' });
-      },
-      world: function(thing, options) {
+      })
+      .withHelper('world', function(thing, options) {
         return options.data.adjective + ' ' + thing + (this.exclaim || '');
-      }
-    };
-
-    var result = template(
-      { exclaim: true, zomg: 'world' },
-      { helpers: helpers, data: { adjective: 'happy', accessData: '#win' } }
-    );
-    equals('#win happy world?', result, 'Data output by helper');
+      })
+      .withInput({ exclaim: true, zomg: 'world' })
+      .withRuntimeOptions({ data: { adjective: 'happy', accessData: '#win' } })
+      .withMessage('Data output by helper')
+      .toCompileTo('#win happy world?');
   });
 
   it('you can override inherited data when invoking a helper', function() {
-    var template = CompilerContext.compile(
-      '{{#hello}}{{world zomg}}{{/hello}}',
-      { data: true }
-    );
-
-    var helpers = {
-      hello: function(options) {
+    expectTemplate('{{#hello}}{{world zomg}}{{/hello}}')
+      .withCompileOptions({ data: true })
+      .withHelper('hello', function(options) {
         return options.fn(
           { exclaim: '?', zomg: 'world' },
           { data: { adjective: 'sad' } }
         );
-      },
-      world: function(thing, options) {
+      })
+      .withHelper('world', function(thing, options) {
         return options.data.adjective + ' ' + thing + (this.exclaim || '');
-      }
-    };
-
-    var result = template(
-      { exclaim: true, zomg: 'planet' },
-      { helpers: helpers, data: { adjective: 'happy' } }
-    );
-    equals('sad world?', result, 'Overriden data output by helper');
+      })
+      .withInput({ exclaim: true, zomg: 'planet' })
+      .withRuntimeOptions({ data: { adjective: 'happy' } })
+      .withMessage('Overriden data output by helper')
+      .toCompileTo('sad world?');
   });
 
   it('you can override inherited data when invoking a helper with depth', function() {
-    var template = CompilerContext.compile(
-      '{{#hello}}{{world ../zomg}}{{/hello}}',
-      { data: true }
-    );
-
-    var helpers = {
-      hello: function(options) {
+    expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
+      .withCompileOptions({ data: true })
+      .withHelper('hello', function(options) {
         return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } });
-      },
-      world: function(thing, options) {
+      })
+      .withHelper('world', function(thing, options) {
         return options.data.adjective + ' ' + thing + (this.exclaim || '');
-      }
-    };
-
-    var result = template(
-      { exclaim: true, zomg: 'world' },
-      { helpers: helpers, data: { adjective: 'happy' } }
-    );
-    equals('sad world?', result, 'Overriden data output by helper');
+      })
+      .withInput({ exclaim: true, zomg: 'world' })
+      .withRuntimeOptions({ data: { adjective: 'happy' } })
+      .withMessage('Overriden data output by helper')
+      .toCompileTo('sad world?');
   });
 
   describe('@root', function() {
     it('the root context can be looked up via @root', function() {
-      var template = CompilerContext.compile('{{@root.foo}}');
-      var result = template({ foo: 'hello' }, { data: {} });
-      equals('hello', result);
-
-      result = template({ foo: 'hello' }, {});
-      equals('hello', result);
+      expectTemplate('{{@root.foo}}')
+        .withInput({ foo: 'hello' })
+        .withRuntimeOptions({ data: {} })
+        .toCompileTo('hello');
+
+      expectTemplate('{{@root.foo}}')
+        .withInput({ foo: 'hello' })
+        .toCompileTo('hello');
     });
+
     it('passed root values take priority', function() {
-      var template = CompilerContext.compile('{{@root.foo}}');
-      var result = template({}, { data: { root: { foo: 'hello' } } });
-      equals('hello', result);
+      expectTemplate('{{@root.foo}}')
+        .withInput({ foo: 'should not be used' })
+        .withRuntimeOptions({ data: { root: { foo: 'hello' } } })
+        .toCompileTo('hello');
     });
   });
 
   describe('nesting', function() {
     it('the root context can be looked up via @root', function() {
-      var template = CompilerContext.compile(
+      expectTemplate(
         '{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}'
-      );
-      var result = template(
-        { foo: 'hello' },
-        {
-          helpers: {
-            helper: function(options) {
-              var frame = Handlebars.createFrame(options.data);
-              frame.depth = options.data.depth + 1;
-              return options.fn(this, { data: frame });
-            }
-          },
+      )
+        .withInput({ foo: 'hello' })
+        .withHelper('helper', function(options) {
+          var frame = Handlebars.createFrame(options.data);
+          frame.depth = options.data.depth + 1;
+          return options.fn(this, { data: frame });
+        })
+        .withRuntimeOptions({
           data: {
             depth: 0
           }
-        }
-      );
-      equals('2 1 0', result);
+        })
+        .toCompileTo('2 1 0');
     });
   });
 });
diff --git a/spec/env/common.js b/spec/env/common.js
index 0ecebcf4..a122f4d6 100644
--- a/spec/env/common.js
+++ b/spec/env/common.js
@@ -129,6 +129,7 @@ function HandlebarsTestBench(templateAsString) {
   this.templateAsString = templateAsString;
   this.helpers = {};
   this.partials = {};
+  this.decorators = {};
   this.input = {};
   this.message =
     'Template' + templateAsString + ' does not evaluate to expected output';
@@ -146,11 +147,43 @@ HandlebarsTestBench.prototype.withHelper = function(name, helperFunction) {
   return this;
 };
 
+HandlebarsTestBench.prototype.withHelpers = function(helperFunctions) {
+  var self = this;
+  Object.keys(helperFunctions).forEach(function(name) {
+    self.withHelper(name, helperFunctions[name]);
+  });
+  return this;
+};
+
 HandlebarsTestBench.prototype.withPartial = function(name, partialAsString) {
   this.partials[name] = partialAsString;
   return this;
 };
 
+HandlebarsTestBench.prototype.withPartials = function(partials) {
+  var self = this;
+  Object.keys(partials).forEach(function(name) {
+    self.withPartial(name, partials[name]);
+  });
+  return this;
+};
+
+HandlebarsTestBench.prototype.withDecorator = function(
+  name,
+  decoratorFunction
+) {
+  this.decorators[name] = decoratorFunction;
+  return this;
+};
+
+HandlebarsTestBench.prototype.withDecorators = function(decorators) {
+  var self = this;
+  Object.keys(decorators).forEach(function(name) {
+    self.withDecorator(name, decorators[name]);
+  });
+  return this;
+};
+
 HandlebarsTestBench.prototype.withCompileOptions = function(compileOptions) {
   this.compileOptions = compileOptions;
   return this;
@@ -167,19 +200,18 @@ HandlebarsTestBench.prototype.withMessage = function(message) {
 };
 
 HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
-  expect(this._compileAndExecute()).to.equal(expectedOutputAsString);
+  expect(this._compileAndExecute()).to.equal(
+    expectedOutputAsString,
+    this.message
+  );
 };
 
 // see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw)
-HandlebarsTestBench.prototype.toThrow = function(
-  errorLike,
-  errMsgMatcher,
-  msg
-) {
+HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher) {
   var self = this;
   expect(function() {
     self._compileAndExecute();
-  }).to.throw(errorLike, errMsgMatcher, msg);
+  }).to.throw(errorLike, errMsgMatcher, this.message);
 };
 
 HandlebarsTestBench.prototype._compileAndExecute = function() {
@@ -202,5 +234,6 @@ HandlebarsTestBench.prototype._combineRuntimeOptions = function() {
   });
   combinedRuntimeOptions.helpers = this.helpers;
   combinedRuntimeOptions.partials = this.partials;
+  combinedRuntimeOptions.decorators = this.decorators;
   return combinedRuntimeOptions;
 };
diff --git a/spec/helpers.js b/spec/helpers.js
index 60140a09..5166d58d 100644
--- a/spec/helpers.js
+++ b/spec/helpers.js
@@ -1,64 +1,45 @@
 describe('helpers', function() {
   it('helper with complex lookup$', function() {
-    var string = '{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}';
-    var hash = {
-      prefix: '/root',
-      goodbyes: [{ text: 'Goodbye', url: 'goodbye' }]
-    };
-    var helpers = {
-      link: function(prefix) {
+    expectTemplate('{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}')
+      .withInput({
+        prefix: '/root',
+        goodbyes: [{ text: 'Goodbye', url: 'goodbye' }]
+      })
+      .withHelper('link', function(prefix) {
         return (
           '<a href="' + prefix + '/' + this.url + '">' + this.text + '</a>'
         );
-      }
-    };
-    shouldCompileTo(
-      string,
-      [hash, helpers],
-      '<a href="/root/goodbye">Goodbye</a>'
-    );
+      })
+      .toCompileTo('<a href="/root/goodbye">Goodbye</a>');
   });
 
   it('helper for raw block gets raw content', function() {
-    var string = '{{{{raw}}}} {{test}} {{{{/raw}}}}';
-    var hash = { test: 'hello' };
-    var helpers = {
-      raw: function(options) {
+    expectTemplate('{{{{raw}}}} {{test}} {{{{/raw}}}}')
+      .withInput({ test: 'hello' })
+      .withHelper('raw', function(options) {
         return options.fn();
-      }
-    };
-    shouldCompileTo(
-      string,
-      [hash, helpers],
-      ' {{test}} ',
-      'raw block helper gets raw content'
-    );
+      })
+      .withMessage('raw block helper gets raw content')
+      .toCompileTo(' {{test}} ');
   });
 
   it('helper for raw block gets parameters', function() {
-    var string = '{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}';
-    var hash = { test: 'hello' };
-    var helpers = {
-      raw: function(a, b, c, options) {
+    expectTemplate('{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}')
+      .withInput({ test: 'hello' })
+      .withHelper('raw', function(a, b, c, options) {
         return options.fn() + a + b + c;
-      }
-    };
-    shouldCompileTo(
-      string,
-      [hash, helpers],
-      ' {{test}} 123',
-      'raw block helper gets raw content'
-    );
+      })
+      .withMessage('raw block helper gets raw content')
+      .toCompileTo(' {{test}} 123');
   });
 
   describe('raw block parsing (with identity helper-function)', function() {
     function runWithIdentityHelper(template, expected) {
-      var helpers = {
-        identity: function(options) {
+      expectTemplate(template)
+        .withHelper('identity', function(options) {
           return options.fn();
-        }
-      };
-      shouldCompileTo(template, [{}, helpers], expected);
+        })
+        .toCompileTo(expected);
     }
 
     it('helper for nested raw block gets raw content', function() {
@@ -92,60 +73,47 @@ describe('helpers', function() {
 
     it('helper for nested raw block throw exception when with missing closing braces', function() {
       var string = '{{{{a}}}} {{{{/a';
-      shouldThrow(function() {
-        Handlebars.compile(string)();
-      });
+      expectTemplate(string).toThrow();
     });
   });
 
   it('helper block with identical context', function() {
-    var string = '{{#goodbyes}}{{name}}{{/goodbyes}}';
-    var hash = { name: 'Alan' };
-    var helpers = {
-      goodbyes: function(options) {
+    expectTemplate('{{#goodbyes}}{{name}}{{/goodbyes}}')
+      .withInput({ name: 'Alan' })
+      .withHelper('goodbyes', function(options) {
         var out = '';
         var byes = ['Goodbye', 'goodbye', 'GOODBYE'];
         for (var i = 0, j = byes.length; i < j; i++) {
           out += byes[i] + ' ' + options.fn(this) + '! ';
         }
         return out;
-      }
-    };
-    shouldCompileTo(
-      string,
-      [hash, helpers],
-      'Goodbye Alan! goodbye Alan! GOODBYE Alan! '
-    );
+      })
+      .toCompileTo('Goodbye Alan! goodbye Alan! GOODBYE Alan! ');
   });
+
   it('helper block with complex lookup expression', function() {
-    var string = '{{#goodbyes}}{{../name}}{{/goodbyes}}';
-    var hash = { name: 'Alan' };
-    var helpers = {
-      goodbyes: function(options) {
+    expectTemplate('{{#goodbyes}}{{../name}}{{/goodbyes}}')
+      .withInput({ name: 'Alan' })
+      .withHelper('goodbyes', function(options) {
         var out = '';
         var byes = ['Goodbye', 'goodbye', 'GOODBYE'];
         for (var i = 0, j = byes.length; i < j; i++) {
           out += byes[i] + ' ' + options.fn({}) + '! ';
         }
         return out;
-      }
-    };
-    shouldCompileTo(
-      string,
-      [hash, helpers],
-      'Goodbye Alan! goodbye Alan! GOODBYE Alan! '
-    );
+      })
+      .toCompileTo('Goodbye Alan! goodbye Alan! GOODBYE Alan! ');
   });
 
   it('helper with complex lookup and nested template', function() {
-    var string =
-      '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}';
-    var hash = {
-      prefix: '/root',
-      goodbyes: [{ text: 'Goodbye', url: 'goodbye' }]
-    };
-    var helpers = {
-      link: function(prefix, options) {
+    expectTemplate(
+      '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}'
+    )
+      .withInput({
+        prefix: '/root',
+        goodbyes: [{ text: 'Goodbye', url: 'goodbye' }]
+      })
+      .withHelper('link', function(prefix, options) {
         return (
           '<a href="' +
           prefix +
@@ -155,25 +123,19 @@ describe('helpers', function() {
           options.fn(this) +
           '</a>'
         );
-      }
-    };
-    shouldCompileToWithPartials(
-      string,
-      [hash, helpers],
-      false,
-      '<a href="/root/goodbye">Goodbye</a>'
-    );
+      })
+      .toCompileTo('<a href="/root/goodbye">Goodbye</a>');
   });
 
   it('helper with complex lookup and nested template in VM+Compiler', function() {
-    var string =
-      '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}';
-    var hash = {
-      prefix: '/root',
-      goodbyes: [{ text: 'Goodbye', url: 'goodbye' }]
-    };
-    var helpers = {
-      link: function(prefix, options) {
+    expectTemplate(
+      '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}'
+    )
+      .withInput({
+        prefix: '/root',
+        goodbyes: [{ text: 'Goodbye', url: 'goodbye' }]
+      })
+      .withHelper('link', function(prefix, options) {
         return (
           '<a href="' +
           prefix +
@@ -183,149 +145,103 @@ describe('helpers', function() {
           options.fn(this) +
           '</a>'
         );
-      }
-    };
-    shouldCompileToWithPartials(
-      string,
-      [hash, helpers],
-      true,
-      '<a href="/root/goodbye">Goodbye</a>'
-    );
+      })
+      .toCompileTo('<a href="/root/goodbye">Goodbye</a>');
   });
+
   it('helper returning undefined value', function() {
-    shouldCompileTo(' {{nothere}}', [{}, { nothere: function() {} }], ' ');
-    shouldCompileTo(
-      ' {{#nothere}}{{/nothere}}',
-      [{}, { nothere: function() {} }],
-      ' '
-    );
+    expectTemplate(' {{nothere}}')
+      .withHelpers({
+        nothere: function() {}
+      })
+      .toCompileTo(' ');
+
+    expectTemplate(' {{#nothere}}{{/nothere}}')
+      .withHelpers({
+        nothere: function() {}
+      })
+      .toCompileTo(' ');
   });
 
   it('block helper', function() {
-    var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
-    var template = CompilerContext.compile(string);
-
-    var result = template(
-      { world: 'world' },
-      {
-        helpers: {
-          goodbyes: function(options) {
-            return options.fn({ text: 'GOODBYE' });
-          }
-        }
-      }
-    );
-    equal(result, 'GOODBYE! cruel world!', 'Block helper executed');
+    expectTemplate('{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!')
+      .withInput({ world: 'world' })
+      .withHelper('goodbyes', function(options) {
+        return options.fn({ text: 'GOODBYE' });
+      })
+      .withMessage('Block helper executed')
+      .toCompileTo('GOODBYE! cruel world!');
   });
 
   it('block helper staying in the same context', function() {
-    var string = '{{#form}}<p>{{name}}</p>{{/form}}';
-    var template = CompilerContext.compile(string);
-
-    var result = template(
-      { name: 'Yehuda' },
-      {
-        helpers: {
-          form: function(options) {
-            return '<form>' + options.fn(this) + '</form>';
-          }
-        }
-      }
-    );
-    equal(
-      result,
-      '<form><p>Yehuda</p></form>',
-      'Block helper executed with current context'
-    );
+    expectTemplate('{{#form}}<p>{{name}}</p>{{/form}}')
+      .withInput({ name: 'Yehuda' })
+      .withHelper('form', function(options) {
+        return '<form>' + options.fn(this) + '</form>';
+      })
+      .withMessage('Block helper executed with current context')
+      .toCompileTo('<form><p>Yehuda</p></form>');
   });
 
   it('block helper should have context in this', function() {
-    var source =
-      '<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>';
     function link(options) {
       return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
     }
-    var data = {
-      people: [
-        { name: 'Alan', id: 1 },
-        { name: 'Yehuda', id: 2 }
-      ]
-    };
 
-    shouldCompileTo(
-      source,
-      [data, { link: link }],
-      '<ul><li><a href="/people/1">Alan</a></li><li><a href="/people/2">Yehuda</a></li></ul>'
-    );
+    expectTemplate(
+      '<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>'
+    )
+      .withInput({
+        people: [
+          { name: 'Alan', id: 1 },
+          { name: 'Yehuda', id: 2 }
+        ]
+      })
+      .withHelper('link', link)
+      .toCompileTo(
+        '<ul><li><a href="/people/1">Alan</a></li><li><a href="/people/2">Yehuda</a></li></ul>'
+      );
   });
 
   it('block helper for undefined value', function() {
-    shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, '');
+    expectTemplate("{{#empty}}shouldn't render{{/empty}}").toCompileTo('');
   });
 
   it('block helper passing a new context', function() {
-    var string = '{{#form yehuda}}<p>{{name}}</p>{{/form}}';
-    var template = CompilerContext.compile(string);
-
-    var result = template(
-      { yehuda: { name: 'Yehuda' } },
-      {
-        helpers: {
-          form: function(context, options) {
-            return '<form>' + options.fn(context) + '</form>';
-          }
-        }
-      }
-    );
-    equal(result, '<form><p>Yehuda</p></form>', 'Context variable resolved');
+    expectTemplate('{{#form yehuda}}<p>{{name}}</p>{{/form}}')
+      .withInput({ yehuda: { name: 'Yehuda' } })
+      .withHelper('form', function(context, options) {
+        return '<form>' + options.fn(context) + '</form>';
+      })
+      .withMessage('Context variable resolved')
+      .toCompileTo('<form><p>Yehuda</p></form>');
   });
 
   it('block helper passing a complex path context', function() {
-    var string = '{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}';
-    var template = CompilerContext.compile(string);
-
-    var result = template(
-      { yehuda: { name: 'Yehuda', cat: { name: 'Harold' } } },
-      {
-        helpers: {
-          form: function(context, options) {
-            return '<form>' + options.fn(context) + '</form>';
-          }
-        }
-      }
-    );
-    equal(
-      result,
-      '<form><p>Harold</p></form>',
-      'Complex path variable resolved'
-    );
+    expectTemplate('{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}')
+      .withInput({ yehuda: { name: 'Yehuda', cat: { name: 'Harold' } } })
+      .withHelper('form', function(context, options) {
+        return '<form>' + options.fn(context) + '</form>';
+      })
+      .withMessage('Complex path variable resolved')
+      .toCompileTo('<form><p>Harold</p></form>');
   });
 
   it('nested block helpers', function() {
-    var string =
-      '{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}';
-    var template = CompilerContext.compile(string);
-
-    var result = template(
-      {
+    expectTemplate(
+      '{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}'
+    )
+      .withInput({
         yehuda: { name: 'Yehuda' }
-      },
-      {
-        helpers: {
-          link: function(options) {
-            return '<a href="' + this.name + '">' + options.fn(this) + '</a>';
-          },
-          form: function(context, options) {
-            return '<form>' + options.fn(context) + '</form>';
-          }
-        }
-      }
-    );
-    equal(
-      result,
-      '<form><p>Yehuda</p><a href="Yehuda">Hello</a></form>',
-      'Both blocks executed'
-    );
+      })
+      .withHelper('link', function(options) {
+        return '<a href="' + this.name + '">' + options.fn(this) + '</a>';
+      })
+      .withHelper('form', function(context, options) {
+        return '<form>' + options.fn(context) + '</form>';
+      })
+      .withMessage('Both blocks executed')
+      .toCompileTo('<form><p>Yehuda</p><a href="Yehuda">Hello</a></form>');
   });
 
   it('block helper inverted sections', function() {
@@ -345,35 +261,28 @@ describe('helpers', function() {
       }
     }
 
-    var hash = { people: [{ name: 'Alan' }, { name: 'Yehuda' }] };
-    var empty = { people: [] };
-    var rootMessage = {
-      people: [],
-      message: "Nobody's here"
-    };
-
-    var messageString = '{{#list people}}Hello{{^}}{{message}}{{/list}}';
-
     // the meaning here may be kind of hard to catch, but list.not is always called,
     // so we should see the output of both
-    shouldCompileTo(
-      string,
-      [hash, { list: list }],
-      '<ul><li>Alan</li><li>Yehuda</li></ul>',
-      'an inverse wrapper is passed in as a new context'
-    );
-    shouldCompileTo(
-      string,
-      [empty, { list: list }],
-      "<p><em>Nobody's here</em></p>",
-      'an inverse wrapper can be optionally called'
-    );
-    shouldCompileTo(
-      messageString,
-      [rootMessage, { list: list }],
-      '<p>Nobody&#x27;s here</p>',
-      'the context of an inverse is the parent of the block'
-    );
+    expectTemplate(string)
+      .withInput({ people: [{ name: 'Alan' }, { name: 'Yehuda' }] })
+      .withHelpers({ list: list })
+      .withMessage('an inverse wrapper is passed in as a new context')
+      .toCompileTo('<ul><li>Alan</li><li>Yehuda</li></ul>');
+
+    expectTemplate(string)
+      .withInput({ people: [] })
+      .withHelpers({ list: list })
+      .withMessage('an inverse wrapper can be optionally called')
+      .toCompileTo("<p><em>Nobody's here</em></p>");
+
+    expectTemplate('{{#list people}}Hello{{^}}{{message}}{{/list}}')
+      .withInput({
+        people: [],
+        message: "Nobody's here"
+      })
+      .withHelpers({ list: list })
+      .withMessage('the context of an inverse is the parent of the block')
+      .toCompileTo('<p>Nobody&#x27;s here</p>');
   });
 
   it('pathed lambas with parameters', function() {
@@ -388,84 +297,73 @@ describe('helpers', function() {
         return 'fail';
       }
     };
-    shouldCompileTo('{{./helper 1}}', [hash, helpers], 'winning');
-    shouldCompileTo('{{hash/helper 1}}', [hash, helpers], 'winning');
+
+    expectTemplate('{{./helper 1}}')
+      .withInput(hash)
+      .withHelpers(helpers)
+      .toCompileTo('winning');
+
+    expectTemplate('{{hash/helper 1}}')
+      .withInput(hash)
+      .withHelpers(helpers)
+      .toCompileTo('winning');
   });
 
   describe('helpers hash', function() {
     it('providing a helpers hash', function() {
-      shouldCompileTo(
-        'Goodbye {{cruel}} {{world}}!',
-        [
-          { cruel: 'cruel' },
-          {
-            world: function() {
-              return 'world';
-            }
+      expectTemplate('Goodbye {{cruel}} {{world}}!')
+        .withInput({ cruel: 'cruel' })
+        .withHelpers({
+          world: function() {
+            return 'world';
           }
-        ],
-        'Goodbye cruel world!',
-        'helpers hash is available'
-      );
-
-      shouldCompileTo(
-        'Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!',
-        [
-          { iter: [{ cruel: 'cruel' }] },
-          {
-            world: function() {
-              return 'world';
-            }
+        })
+        .withMessage('helpers hash is available')
+        .toCompileTo('Goodbye cruel world!');
+
+      expectTemplate('Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!')
+        .withInput({ iter: [{ cruel: 'cruel' }] })
+        .withHelpers({
+          world: function() {
+            return 'world';
           }
-        ],
-        'Goodbye cruel world!',
-        'helpers hash is available inside other blocks'
-      );
+        })
+        .withMessage('helpers hash is available inside other blocks')
+        .toCompileTo('Goodbye cruel world!');
     });
 
     it('in cases of conflict, helpers win', function() {
-      shouldCompileTo(
-        '{{{lookup}}}',
-        [
-          { lookup: 'Explicit' },
-          {
-            lookup: function() {
-              return 'helpers';
-            }
+      expectTemplate('{{{lookup}}}')
+        .withInput({ lookup: 'Explicit' })
+        .withHelpers({
+          lookup: function() {
+            return 'helpers';
           }
-        ],
-        'helpers',
-        'helpers hash has precedence escaped expansion'
-      );
-      shouldCompileTo(
-        '{{lookup}}',
-        [
-          { lookup: 'Explicit' },
-          {
-            lookup: function() {
-              return 'helpers';
-            }
+        })
+        .withMessage('helpers hash has precedence escaped expansion')
+        .toCompileTo('helpers');
+
+      expectTemplate('{{lookup}}')
+        .withInput({ lookup: 'Explicit' })
+        .withHelpers({
+          lookup: function() {
+            return 'helpers';
           }
-        ],
-        'helpers',
-        'helpers hash has precedence simple expansion'
-      );
+        })
+        .withMessage('helpers hash has precedence simple expansion')
+        .toCompileTo('helpers');
     });
 
     it('the helpers hash is available is nested contexts', function() {
-      shouldCompileTo(
-        '{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}',
-        [
-          { outer: { inner: { unused: [] } } },
-          {
-            helper: function() {
-              return 'helper';
-            }
+      expectTemplate('{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}')
+        .withInput({ outer: { inner: { unused: [] } } })
+        .withHelpers({
+          helper: function() {
+            return 'helper';
           }
-        ],
-        'helper',
-        'helpers hash is available in nested contexts.'
-      );
+        })
+        .withMessage('helpers hash is available in nested contexts.')
+        .toCompileTo('helper');
     });
 
     it('the helper hash should augment the global hash', function() {
@@ -473,18 +371,16 @@ describe('helpers', function() {
         return 'found it!';
       });
 
-      shouldCompileTo(
-        '{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}',
-        [
-          { cruel: 'cruel' },
-          {
-            world: function() {
-              return 'world!';
-            }
+      expectTemplate(
+        '{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}'
+      )
+        .withInput({ cruel: 'cruel' })
+        .withHelpers({
+          world: function() {
+            return 'world!';
           }
-        ],
-        'found it! Goodbye cruel world!!'
-      );
+        })
+        .toCompileTo('found it! Goodbye cruel world!!');
     });
   });
 
@@ -513,12 +409,13 @@ describe('helpers', function() {
         }
       });
 
-      shouldCompileTo(
-        '{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}',
-        [{ cruel: 'cruel' }],
-        'found it! Goodbye cruel world!!'
-      );
+      expectTemplate(
+        '{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}'
+      )
+        .withInput({ cruel: 'cruel' })
+        .toCompileTo('found it! Goodbye cruel world!!');
     });
+
     it('fails with multiple and args', function() {
       shouldThrow(
         function() {
@@ -541,9 +438,8 @@ describe('helpers', function() {
   });
 
   it('decimal number literals work', function() {
-    var string = 'Message: {{hello -1.2 1.2}}';
-    var helpers = {
-      hello: function(times, times2) {
+    expectTemplate('Message: {{hello -1.2 1.2}}')
+      .withHelper('hello', function(times, times2) {
         if (typeof times !== 'number') {
           times = 'NaN';
         }
@@ -551,39 +447,27 @@ describe('helpers', function() {
           times2 = 'NaN';
         }
         return 'Hello ' + times + ' ' + times2 + ' times';
-      }
-    };
-    shouldCompileTo(
-      string,
-      [{}, helpers],
-      'Message: Hello -1.2 1.2 times',
-      'template with a negative integer literal'
-    );
+      })
+      .withMessage('template with a negative integer literal')
+      .toCompileTo('Message: Hello -1.2 1.2 times');
   });
 
   it('negative number literals work', function() {
-    var string = 'Message: {{hello -12}}';
-    var helpers = {
-      hello: function(times) {
+    expectTemplate('Message: {{hello -12}}')
+      .withHelper('hello', function(times) {
         if (typeof times !== 'number') {
           times = 'NaN';
         }
         return 'Hello ' + times + ' times';
-      }
-    };
-    shouldCompileTo(
-      string,
-      [{}, helpers],
-      'Message: Hello -12 times',
-      'template with a negative integer literal'
-    );
+      })
+      .withMessage('template with a negative integer literal')
+      .toCompileTo('Message: Hello -12 times');
   });
 
   describe('String literal parameters', function() {
     it('simple literals work', function() {
-      var string = 'Message: {{hello "world" 12 true false}}';
-      var helpers = {
-        hello: function(param, times, bool1, bool2) {
+      expectTemplate('Message: {{hello "world" 12 true false}}')
+        .withHelper('hello', function(param, times, bool1, bool2) {
           if (typeof times !== 'number') {
             times = 'NaN';
           }
@@ -596,115 +480,74 @@ describe('helpers', function() {
           return (
             'Hello ' + param + ' ' + times + ' times: ' + bool1 + ' ' + bool2
           );
-        }
-      };
-      shouldCompileTo(
-        string,
-        [{}, helpers],
-        'Message: Hello world 12 times: true false',
-        'template with a simple String literal'
-      );
+        })
+        .withMessage('template with a simple String literal')
+        .toCompileTo('Message: Hello world 12 times: true false');
     });
 
     it('using a quote in the middle of a parameter raises an error', function() {
-      var string = 'Message: {{hello wo"rld"}}';
-      shouldThrow(function() {
-        CompilerContext.compile(string);
-      }, Error);
+      expectTemplate('Message: {{hello wo"rld"}}').toThrow(Error);
     });
 
     it('escaping a String is possible', function() {
-      var string = 'Message: {{{hello "\\"world\\""}}}';
-      var helpers = {
-        hello: function(param) {
+      expectTemplate('Message: {{{hello "\\"world\\""}}}')
+        .withHelper('hello', function(param) {
           return 'Hello ' + param;
-        }
-      };
-      shouldCompileTo(
-        string,
-        [{}, helpers],
-        'Message: Hello "world"',
-        'template with an escaped String literal'
-      );
+        })
+        .withMessage('template with an escaped String literal')
+        .toCompileTo('Message: Hello "world"');
     });
 
     it("it works with ' marks", function() {
-      var string = 'Message: {{{hello "Alan\'s world"}}}';
-      var helpers = {
-        hello: function(param) {
+      expectTemplate('Message: {{{hello "Alan\'s world"}}}')
+        .withHelper('hello', function(param) {
           return 'Hello ' + param;
-        }
-      };
-      shouldCompileTo(
-        string,
-        [{}, helpers],
-        "Message: Hello Alan's world",
-        "template with a ' mark"
-      );
+        })
+        .withMessage("template with a ' mark")
+        .toCompileTo("Message: Hello Alan's world");
     });
   });
 
   it('negative number literals work', function() {
-    var string = 'Message: {{hello -12}}';
-    var helpers = {
-      hello: function(times) {
+    expectTemplate('Message: {{hello -12}}')
+      .withHelper('hello', function(times) {
         if (typeof times !== 'number') {
           times = 'NaN';
         }
         return 'Hello ' + times + ' times';
-      }
-    };
-    shouldCompileTo(
-      string,
-      [{}, helpers],
-      'Message: Hello -12 times',
-      'template with a negative integer literal'
-    );
+      })
+      .withMessage('template with a negative integer literal')
+      .toCompileTo('Message: Hello -12 times');
   });
 
   describe('multiple parameters', function() {
     it('simple multi-params work', function() {
-      var string = 'Message: {{goodbye cruel world}}';
-      var hash = { cruel: 'cruel', world: 'world' };
-      var helpers = {
-        goodbye: function(cruel, world) {
+      expectTemplate('Message: {{goodbye cruel world}}')
+        .withInput({ cruel: 'cruel', world: 'world' })
+        .withHelper('goodbye', function(cruel, world) {
           return 'Goodbye ' + cruel + ' ' + world;
-        }
-      };
-      shouldCompileTo(
-        string,
-        [hash, helpers],
-        'Message: Goodbye cruel world',
-        'regular helpers with multiple params'
-      );
+        })
+        .withMessage('regular helpers with multiple params')
+        .toCompileTo('Message: Goodbye cruel world');
     });
 
     it('block multi-params work', function() {
-      var string =
-        'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}';
-      var hash = { cruel: 'cruel', world: 'world' };
-      var helpers = {
-        goodbye: function(cruel, world, options) {
+      expectTemplate(
+        'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}'
+      )
+        .withInput({ cruel: 'cruel', world: 'world' })
+        .withHelper('goodbye', function(cruel, world, options) {
           return options.fn({ greeting: 'Goodbye', adj: cruel, noun: world });
-        }
-      };
-      shouldCompileTo(
-        string,
-        [hash, helpers],
-        'Message: Goodbye cruel world',
-        'block helpers with multiple params'
-      );
+        })
+        .withMessage('block helpers with multiple params')
+        .toCompileTo('Message: Goodbye cruel world');
     });
   });
 
   describe('hash', function() {
     it('helpers can take an optional hash', function() {
-      var template = CompilerContext.compile(
-        '{{goodbye cruel="CRUEL" world="WORLD" times=12}}'
-      );
-
-      var helpers = {
-        goodbye: function(options) {
+      expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" times=12}}')
+        .withHelper('goodbye', function(options) {
           return (
             'GOODBYE ' +
             options.hash.cruel +
@@ -714,50 +557,36 @@ describe('helpers', function() {
             options.hash.times +
             ' TIMES'
           );
-        }
-      };
-
-      var context = {};
-
-      var result = template(context, { helpers: helpers });
-      equals(result, 'GOODBYE CRUEL WORLD 12 TIMES', 'Helper output hash');
+        })
+        .withMessage('Helper output hash')
+        .toCompileTo('GOODBYE CRUEL WORLD 12 TIMES');
     });
 
     it('helpers can take an optional hash with booleans', function() {
-      var helpers = {
-        goodbye: function(options) {
-          if (options.hash.print === true) {
-            return 'GOODBYE ' + options.hash.cruel + ' ' + options.hash.world;
-          } else if (options.hash.print === false) {
-            return 'NOT PRINTING';
-          } else {
-            return 'THIS SHOULD NOT HAPPEN';
-          }
+      function goodbye(options) {
+        if (options.hash.print === true) {
+          return 'GOODBYE ' + options.hash.cruel + ' ' + options.hash.world;
+        } else if (options.hash.print === false) {
+          return 'NOT PRINTING';
+        } else {
+          return 'THIS SHOULD NOT HAPPEN';
         }
-      };
-
-      var context = {};
+      }
 
-      var template = CompilerContext.compile(
-        '{{goodbye cruel="CRUEL" world="WORLD" print=true}}'
-      );
-      var result = template(context, { helpers: helpers });
-      equals(result, 'GOODBYE CRUEL WORLD', 'Helper output hash');
+      expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" print=true}}')
+        .withHelper('goodbye', goodbye)
+        .withMessage('Helper output hash')
+        .toCompileTo('GOODBYE CRUEL WORLD');
 
-      template = CompilerContext.compile(
-        '{{goodbye cruel="CRUEL" world="WORLD" print=false}}'
-      );
-      result = template(context, { helpers: helpers });
-      equals(result, 'NOT PRINTING', 'Boolean helper parameter honored');
+      expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" print=false}}')
+        .withHelper('goodbye', goodbye)
+        .withMessage('Boolean helper parameter honored')
+        .toCompileTo('NOT PRINTING');
     });
 
     it('block helpers can take an optional hash', function() {
-      var template = CompilerContext.compile(
-        '{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}'
-      );
-
-      var helpers = {
-        goodbye: function(options) {
+      expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}')
+        .withHelper('goodbye', function(options) {
           return (
             'GOODBYE ' +
             options.hash.cruel +
@@ -767,20 +596,14 @@ describe('helpers', function() {
             options.hash.times +
             ' TIMES'
           );
-        }
-      };
-
-      var result = template({}, { helpers: helpers });
-      equals(result, 'GOODBYE CRUEL world 12 TIMES', 'Hash parameters output');
+        })
+        .withMessage('Hash parameters output')
+        .toCompileTo('GOODBYE CRUEL world 12 TIMES');
     });
 
     it('block helpers can take an optional hash with single quoted stings', function() {
-      var template = CompilerContext.compile(
-        '{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}'
-      );
-
-      var helpers = {
-        goodbye: function(options) {
+      expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}')
+        .withHelper('goodbye', function(options) {
           return (
             'GOODBYE ' +
             options.hash.cruel +
@@ -790,200 +613,173 @@ describe('helpers', function() {
             options.hash.times +
             ' TIMES'
           );
-        }
-      };
-
-      var result = template({}, { helpers: helpers });
-      equals(result, 'GOODBYE CRUEL world 12 TIMES', 'Hash parameters output');
+        })
+        .withMessage('Hash parameters output')
+        .toCompileTo('GOODBYE CRUEL world 12 TIMES');
     });
 
     it('block helpers can take an optional hash with booleans', function() {
-      var helpers = {
-        goodbye: function(options) {
-          if (options.hash.print === true) {
-            return 'GOODBYE ' + options.hash.cruel + ' ' + options.fn(this);
-          } else if (options.hash.print === false) {
-            return 'NOT PRINTING';
-          } else {
-            return 'THIS SHOULD NOT HAPPEN';
-          }
+      function goodbye(options) {
+        if (options.hash.print === true) {
+          return 'GOODBYE ' + options.hash.cruel + ' ' + options.fn(this);
+        } else if (options.hash.print === false) {
+          return 'NOT PRINTING';
+        } else {
+          return 'THIS SHOULD NOT HAPPEN';
         }
-      };
+      }
 
-      var template = CompilerContext.compile(
-        '{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}'
-      );
-      var result = template({}, { helpers: helpers });
-      equals(result, 'GOODBYE CRUEL world', 'Boolean hash parameter honored');
+      expectTemplate('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}')
+        .withHelper('goodbye', goodbye)
+        .withMessage('Boolean hash parameter honored')
+        .toCompileTo('GOODBYE CRUEL world');
 
-      template = CompilerContext.compile(
-        '{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}'
-      );
-      result = template({}, { helpers: helpers });
-      equals(result, 'NOT PRINTING', 'Boolean hash parameter honored');
+      expectTemplate('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}')
+        .withHelper('goodbye', goodbye)
+        .withMessage('Boolean hash parameter honored')
+        .toCompileTo('NOT PRINTING');
     });
   });
 
   describe('helperMissing', function() {
     it('if a context is not found, helperMissing is used', function() {
-      shouldThrow(
-        function() {
-          var template = CompilerContext.compile('{{hello}} {{link_to world}}');
-          template({});
-        },
-        undefined,
+      expectTemplate('{{hello}} {{link_to world}}').toThrow(
         /Missing helper: "link_to"/
       );
     });
 
     it('if a context is not found, custom helperMissing is used', function() {
-      var string = '{{hello}} {{link_to world}}';
-      var context = { hello: 'Hello', world: 'world' };
-
-      var helpers = {
-        helperMissing: function(mesg, options) {
+      expectTemplate('{{hello}} {{link_to world}}')
+        .withInput({ hello: 'Hello', world: 'world' })
+        .withHelper('helperMissing', function(mesg, options) {
           if (options.name === 'link_to') {
             return new Handlebars.SafeString('<a>' + mesg + '</a>');
           }
-        }
-      };
-
-      shouldCompileTo(string, [context, helpers], 'Hello <a>world</a>');
+        })
+        .toCompileTo('Hello <a>world</a>');
     });
 
     it('if a value is not found, custom helperMissing is used', function() {
-      var string = '{{hello}} {{link_to}}';
-      var context = { hello: 'Hello', world: 'world' };
-
-      var helpers = {
-        helperMissing: function(options) {
+      expectTemplate('{{hello}} {{link_to}}')
+        .withInput({ hello: 'Hello', world: 'world' })
+        .withHelper('helperMissing', function(options) {
           if (options.name === 'link_to') {
             return new Handlebars.SafeString('<a>winning</a>');
           }
-        }
-      };
-
-      shouldCompileTo(string, [context, helpers], 'Hello <a>winning</a>');
+        })
+        .toCompileTo('Hello <a>winning</a>');
     });
   });
 
   describe('knownHelpers', function() {
     it('Known helper should render helper', function() {
-      var template = CompilerContext.compile('{{hello}}', {
-        knownHelpers: { hello: true }
-      });
-
-      var result = template(
-        {},
-        {
-          helpers: {
-            hello: function() {
-              return 'foo';
-            }
-          }
-        }
-      );
-      equal(result, 'foo', "'foo' should === '" + result);
+      expectTemplate('{{hello}}')
+        .withCompileOptions({
+          knownHelpers: { hello: true }
+        })
+        .withHelper('hello', function() {
+          return 'foo';
+        })
+        .toCompileTo('foo');
     });
 
     it('Unknown helper in knownHelpers only mode should be passed as undefined', function() {
-      var template = CompilerContext.compile('{{typeof hello}}', {
-        knownHelpers: { typeof: true },
-        knownHelpersOnly: true
-      });
-
-      var result = template(
-        {},
-        {
-          helpers: {
-            typeof: function(arg) {
-              return typeof arg;
-            },
-            hello: function() {
-              return 'foo';
-            }
-          }
-        }
-      );
-      equal(result, 'undefined', "'undefined' should === '" + result);
+      expectTemplate('{{typeof hello}}')
+        .withCompileOptions({
+          knownHelpers: { typeof: true },
+          knownHelpersOnly: true
+        })
+        .withHelper('typeof', function(arg) {
+          return typeof arg;
+        })
+        .withHelper('hello', function() {
+          return 'foo';
+        })
+        .toCompileTo('undefined');
     });
-    it('Builtin helpers available in knownHelpers only mode', function() {
-      var template = CompilerContext.compile('{{#unless foo}}bar{{/unless}}', {
-        knownHelpersOnly: true
-      });
 
-      var result = template({});
-      equal(result, 'bar', "'bar' should === '" + result);
+    it('Builtin helpers available in knownHelpers only mode', function() {
+      expectTemplate('{{#unless foo}}bar{{/unless}}')
+        .withCompileOptions({
+          knownHelpersOnly: true
+        })
+        .toCompileTo('bar');
     });
-    it('Field lookup works in knownHelpers only mode', function() {
-      var template = CompilerContext.compile('{{foo}}', {
-        knownHelpersOnly: true
-      });
 
-      var result = template({ foo: 'bar' });
-      equal(result, 'bar', "'bar' should === '" + result);
+    it('Field lookup works in knownHelpers only mode', function() {
+      expectTemplate('{{foo}}')
+        .withCompileOptions({
+          knownHelpersOnly: true
+        })
+        .withInput({ foo: 'bar' })
+        .toCompileTo('bar');
     });
-    it('Conditional blocks work in knownHelpers only mode', function() {
-      var template = CompilerContext.compile('{{#foo}}bar{{/foo}}', {
-        knownHelpersOnly: true
-      });
 
-      var result = template({ foo: 'baz' });
-      equal(result, 'bar', "'bar' should === '" + result);
+    it('Conditional blocks work in knownHelpers only mode', function() {
+      expectTemplate('{{#foo}}bar{{/foo}}')
+        .withCompileOptions({
+          knownHelpersOnly: true
+        })
+        .withInput({ foo: 'baz' })
+        .toCompileTo('bar');
     });
-    it('Invert blocks work in knownHelpers only mode', function() {
-      var template = CompilerContext.compile('{{^foo}}bar{{/foo}}', {
-        knownHelpersOnly: true
-      });
 
-      var result = template({ foo: false });
-      equal(result, 'bar', "'bar' should === '" + result);
+    it('Invert blocks work in knownHelpers only mode', function() {
+      expectTemplate('{{^foo}}bar{{/foo}}')
+        .withCompileOptions({
+          knownHelpersOnly: true
+        })
+        .withInput({ foo: false })
+        .toCompileTo('bar');
     });
+
     it('Functions are bound to the context in knownHelpers only mode', function() {
-      var template = CompilerContext.compile('{{foo}}', {
-        knownHelpersOnly: true
-      });
-      var result = template({
-        foo: function() {
-          return this.bar;
-        },
-        bar: 'bar'
-      });
-      equal(result, 'bar', "'bar' should === '" + result);
+      expectTemplate('{{foo}}')
+        .withCompileOptions({
+          knownHelpersOnly: true
+        })
+        .withInput({
+          foo: function() {
+            return this.bar;
+          },
+          bar: 'bar'
+        })
+        .toCompileTo('bar');
     });
+
     it('Unknown helper call in knownHelpers only mode should throw', function() {
-      shouldThrow(function() {
-        CompilerContext.compile('{{typeof hello}}', { knownHelpersOnly: true });
-      }, Error);
+      expectTemplate('{{typeof hello}}')
+        .withCompileOptions({ knownHelpersOnly: true })
+        .toThrow(Error);
     });
   });
 
   describe('blockHelperMissing', function() {
     it('lambdas are resolved by blockHelperMissing, not handlebars proper', function() {
-      var string = '{{#truthy}}yep{{/truthy}}';
-      var data = {
-        truthy: function() {
-          return true;
-        }
-      };
-      shouldCompileTo(string, data, 'yep');
+      expectTemplate('{{#truthy}}yep{{/truthy}}')
+        .withInput({
+          truthy: function() {
+            return true;
+          }
+        })
+        .toCompileTo('yep');
     });
+
     it('lambdas resolved by blockHelperMissing are bound to the context', function() {
-      var string = '{{#truthy}}yep{{/truthy}}';
-      var boundData = {
-        truthy: function() {
-          return this.truthiness();
-        },
-        truthiness: function() {
-          return false;
-        }
-      };
-      shouldCompileTo(string, boundData, '');
+      expectTemplate('{{#truthy}}yep{{/truthy}}')
+        .withInput({
+          truthy: function() {
+            return this.truthiness();
+          },
+          truthiness: function() {
+            return false;
+          }
+        })
+        .toCompileTo('');
     });
   });
 
   describe('name field', function() {
-    var context = {};
     var helpers = {
       blockHelperMissing: function() {
         return 'missing: ' + arguments[arguments.length - 1].name;
@@ -997,212 +793,173 @@ describe('helpers', function() {
     };
 
     it('should include in ambiguous mustache calls', function() {
-      shouldCompileTo('{{helper}}', [context, helpers], 'ran: helper');
+      expectTemplate('{{helper}}')
+        .withHelpers(helpers)
+        .toCompileTo('ran: helper');
     });
+
     it('should include in helper mustache calls', function() {
-      shouldCompileTo('{{helper 1}}', [context, helpers], 'ran: helper');
+      expectTemplate('{{helper 1}}')
+        .withHelpers(helpers)
+        .toCompileTo('ran: helper');
     });
+
     it('should include in ambiguous block calls', function() {
-      shouldCompileTo(
-        '{{#helper}}{{/helper}}',
-        [context, helpers],
-        'ran: helper'
-      );
+      expectTemplate('{{#helper}}{{/helper}}')
+        .withHelpers(helpers)
+        .toCompileTo('ran: helper');
     });
+
     it('should include in simple block calls', function() {
-      shouldCompileTo(
-        '{{#./helper}}{{/./helper}}',
-        [context, helpers],
-        'missing: ./helper'
-      );
+      expectTemplate('{{#./helper}}{{/./helper}}')
+        .withHelpers(helpers)
+        .toCompileTo('missing: ./helper');
     });
+
     it('should include in helper block calls', function() {
-      shouldCompileTo(
-        '{{#helper 1}}{{/helper}}',
-        [context, helpers],
-        'ran: helper'
-      );
+      expectTemplate('{{#helper 1}}{{/helper}}')
+        .withHelpers(helpers)
+        .toCompileTo('ran: helper');
     });
-    it('should include in known helper calls', function() {
-      var template = CompilerContext.compile('{{helper}}', {
-        knownHelpers: { helper: true },
-        knownHelpersOnly: true
-      });
 
-      equal(template({}, { helpers: helpers }), 'ran: helper');
+    it('should include in known helper calls', function() {
+      expectTemplate('{{helper}}')
+        .withCompileOptions({
+          knownHelpers: { helper: true },
+          knownHelpersOnly: true
+        })
+        .withHelpers(helpers)
+        .toCompileTo('ran: helper');
     });
 
     it('should include full id', function() {
-      shouldCompileTo(
-        '{{#foo.helper}}{{/foo.helper}}',
-        [{ foo: {} }, helpers],
-        'missing: foo.helper'
-      );
+      expectTemplate('{{#foo.helper}}{{/foo.helper}}')
+        .withInput({ foo: {} })
+        .withHelpers(helpers)
+        .toCompileTo('missing: foo.helper');
     });
 
     it('should include full id if a hash is passed', function() {
-      shouldCompileTo(
-        '{{#foo.helper bar=baz}}{{/foo.helper}}',
-        [{ foo: {} }, helpers],
-        'helper missing: foo.helper'
-      );
+      expectTemplate('{{#foo.helper bar=baz}}{{/foo.helper}}')
+        .withInput({ foo: {} })
+        .withHelpers(helpers)
+        .toCompileTo('helper missing: foo.helper');
     });
   });
 
   describe('name conflicts', function() {
     it('helpers take precedence over same-named context properties', function() {
-      var template = CompilerContext.compile('{{goodbye}} {{cruel world}}');
-
-      var helpers = {
-        goodbye: function() {
+      expectTemplate('{{goodbye}} {{cruel world}}')
+        .withHelper('goodbye', function() {
           return this.goodbye.toUpperCase();
-        },
-
-        cruel: function(world) {
+        })
+        .withHelper('cruel', function(world) {
           return 'cruel ' + world.toUpperCase();
-        }
-      };
-
-      var context = {
-        goodbye: 'goodbye',
-        world: 'world'
-      };
-
-      var result = template(context, { helpers: helpers });
-      equals(result, 'GOODBYE cruel WORLD', 'Helper executed');
+        })
+        .withInput({
+          goodbye: 'goodbye',
+          world: 'world'
+        })
+        .withMessage('Helper executed')
+        .toCompileTo('GOODBYE cruel WORLD');
     });
 
     it('helpers take precedence over same-named context properties$', function() {
-      var template = CompilerContext.compile(
-        '{{#goodbye}} {{cruel world}}{{/goodbye}}'
-      );
-
-      var helpers = {
-        goodbye: function(options) {
+      expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}}')
+        .withHelper('goodbye', function(options) {
           return this.goodbye.toUpperCase() + options.fn(this);
-        },
-
-        cruel: function(world) {
+        })
+        .withHelper('cruel', function(world) {
           return 'cruel ' + world.toUpperCase();
-        }
-      };
-
-      var context = {
-        goodbye: 'goodbye',
-        world: 'world'
-      };
-
-      var result = template(context, { helpers: helpers });
-      equals(result, 'GOODBYE cruel WORLD', 'Helper executed');
+        })
+        .withInput({
+          goodbye: 'goodbye',
+          world: 'world'
+        })
+        .withMessage('Helper executed')
+        .toCompileTo('GOODBYE cruel WORLD');
     });
 
     it('Scoped names take precedence over helpers', function() {
-      var template = CompilerContext.compile(
-        '{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}'
-      );
-
-      var helpers = {
-        goodbye: function() {
+      expectTemplate('{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}')
+        .withHelper('goodbye', function() {
           return this.goodbye.toUpperCase();
-        },
-
-        cruel: function(world) {
+        })
+        .withHelper('cruel', function(world) {
           return 'cruel ' + world.toUpperCase();
-        }
-      };
-
-      var context = {
-        goodbye: 'goodbye',
-        world: 'world'
-      };
-
-      var result = template(context, { helpers: helpers });
-      equals(
-        result,
-        'goodbye cruel WORLD cruel GOODBYE',
-        'Helper not executed'
-      );
+        })
+        .withInput({
+          goodbye: 'goodbye',
+          world: 'world'
+        })
+        .withMessage('Helper not executed')
+        .toCompileTo('goodbye cruel WORLD cruel GOODBYE');
     });
 
     it('Scoped names take precedence over block helpers', function() {
-      var template = CompilerContext.compile(
+      expectTemplate(
         '{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}'
-      );
-
-      var helpers = {
-        goodbye: function(options) {
+      )
+        .withHelper('goodbye', function(options) {
           return this.goodbye.toUpperCase() + options.fn(this);
-        },
-
-        cruel: function(world) {
+        })
+        .withHelper('cruel', function(world) {
           return 'cruel ' + world.toUpperCase();
-        }
-      };
-
-      var context = {
-        goodbye: 'goodbye',
-        world: 'world'
-      };
-
-      var result = template(context, { helpers: helpers });
-      equals(result, 'GOODBYE cruel WORLD goodbye', 'Helper executed');
+        })
+        .withInput({
+          goodbye: 'goodbye',
+          world: 'world'
+        })
+        .withMessage('Helper executed')
+        .toCompileTo('GOODBYE cruel WORLD goodbye');
     });
   });
 
   describe('block params', function() {
     it('should take presedence over context values', function() {
-      var hash = { value: 'foo' };
-      var helpers = {
-        goodbyes: function(options) {
+      expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}')
+        .withInput({ value: 'foo' })
+        .withHelper('goodbyes', function(options) {
           equals(options.fn.blockParams, 1);
           return options.fn({ value: 'bar' }, { blockParams: [1, 2] });
-        }
-      };
-      shouldCompileTo(
-        '{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}',
-        [hash, helpers],
-        '1foo'
-      );
+        })
+        .toCompileTo('1foo');
     });
+
     it('should take presedence over helper values', function() {
-      var hash = {};
-      var helpers = {
-        value: function() {
+      expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}')
+        .withHelper('value', function() {
           return 'foo';
-        },
-        goodbyes: function(options) {
+        })
+        .withHelper('goodbyes', function(options) {
           equals(options.fn.blockParams, 1);
           return options.fn({}, { blockParams: [1, 2] });
-        }
-      };
-      shouldCompileTo(
-        '{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}',
-        [hash, helpers],
-        '1foo'
-      );
+        })
+        .toCompileTo('1foo');
     });
+
     it('should not take presedence over pathed values', function() {
-      var hash = { value: 'bar' };
-      var helpers = {
-        value: function() {
+      expectTemplate(
+        '{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}'
+      )
+        .withInput({ value: 'bar' })
+        .withHelper('value', function() {
           return 'foo';
-        },
-        goodbyes: function(options) {
+        })
+        .withHelper('goodbyes', function(options) {
           equals(options.fn.blockParams, 1);
           return options.fn(this, { blockParams: [1, 2] });
-        }
-      };
-      shouldCompileTo(
-        '{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}',
-        [hash, helpers],
-        'barfoo'
-      );
+        })
+        .toCompileTo('barfoo');
     });
+
     it('should take presednece over parent block params', function() {
-      var hash = { value: 'foo' },
-        value = 1;
-      var helpers = {
-        goodbyes: function(options) {
+      var value = 1;
+      expectTemplate(
+        '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}'
+      )
+        .withInput({ value: 'foo' })
+        .withHelper('goodbyes', function(options) {
           return options.fn(
             { value: 'bar' },
             {
@@ -1210,120 +967,68 @@ describe('helpers', function() {
                 options.fn.blockParams === 1 ? [value++, value++] : undefined
             }
           );
-        }
-      };
-      shouldCompileTo(
-        '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}',
-        [hash, helpers],
-        '13foo'
-      );
+        })
+        .toCompileTo('13foo');
     });
 
     it('should allow block params on chained helpers', function() {
-      var hash = { value: 'foo' };
-      var helpers = {
-        goodbyes: function(options) {
+      expectTemplate(
+        '{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}'
+      )
+        .withInput({ value: 'foo' })
+        .withHelper('goodbyes', function(options) {
           equals(options.fn.blockParams, 1);
           return options.fn({ value: 'bar' }, { blockParams: [1, 2] });
-        }
-      };
-      shouldCompileTo(
-        '{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}',
-        [hash, helpers],
-        '1foo'
-      );
+        })
+        .toCompileTo('1foo');
     });
   });
 
   describe('built-in helpers malformed arguments ', function() {
     it('if helper - too few arguments', function() {
-      var template = CompilerContext.compile('{{#if}}{{/if}}');
-      shouldThrow(
-        function() {
-          template({});
-        },
-        undefined,
+      expectTemplate('{{#if}}{{/if}}').toThrow(
         /#if requires exactly one argument/
       );
     });
 
     it('if helper - too many arguments, string', function() {
-      var template = CompilerContext.compile('{{#if test "string"}}{{/if}}');
-      shouldThrow(
-        function() {
-          template({});
-        },
-        undefined,
+      expectTemplate('{{#if test "string"}}{{/if}}').toThrow(
         /#if requires exactly one argument/
       );
     });
 
     it('if helper - too many arguments, undefined', function() {
-      var template = CompilerContext.compile('{{#if test undefined}}{{/if}}');
-      shouldThrow(
-        function() {
-          template({});
-        },
-        undefined,
+      expectTemplate('{{#if test undefined}}{{/if}}').toThrow(
         /#if requires exactly one argument/
       );
     });
 
     it('if helper - too many arguments, null', function() {
-      var template = CompilerContext.compile('{{#if test null}}{{/if}}');
-      shouldThrow(
-        function() {
-          template({});
-        },
-        undefined,
+      expectTemplate('{{#if test null}}{{/if}}').toThrow(
         /#if requires exactly one argument/
       );
     });
 
     it('unless helper - too few arguments', function() {
-      var template = CompilerContext.compile('{{#unless}}{{/unless}}');
-      shouldThrow(
-        function() {
-          template({});
-        },
-        undefined,
+      expectTemplate('{{#unless}}{{/unless}}').toThrow(
         /#unless requires exactly one argument/
       );
     });
 
     it('unless helper - too many arguments', function() {
-      var template = CompilerContext.compile(
-        '{{#unless test null}}{{/unless}}'
-      );
-      shouldThrow(
-        function() {
-          template({});
-        },
-        undefined,
+      expectTemplate('{{#unless test null}}{{/unless}}').toThrow(
         /#unless requires exactly one argument/
       );
     });
 
     it('with helper - too few arguments', function() {
-      var template = CompilerContext.compile('{{#with}}{{/with}}');
-      shouldThrow(
-        function() {
-          template({});
-        },
-        undefined,
+      expectTemplate('{{#with}}{{/with}}').toThrow(
         /#with requires exactly one argument/
       );
     });
 
     it('with helper - too many arguments', function() {
-      var template = CompilerContext.compile(
-        '{{#with test "string"}}{{/with}}'
-      );
-      shouldThrow(
-        function() {
-          template({});
-        },
-        undefined,
+      expectTemplate('{{#with test "string"}}{{/with}}').toThrow(
         /#with requires exactly one argument/
       );
     });
diff --git a/spec/javascript-compiler.js b/spec/javascript-compiler.js
index e97cbb08..ed2dc8c5 100644
--- a/spec/javascript-compiler.js
+++ b/spec/javascript-compiler.js
@@ -20,14 +20,18 @@ describe('javascript-compiler api', function() {
         return parent + '.bar_' + name;
       };
       /* eslint-disable camelcase */
-      shouldCompileTo('{{foo}}', { bar_foo: 'food' }, 'food');
+      expectTemplate('{{foo}}')
+        .withInput({ bar_foo: 'food' })
+        .toCompileTo('food');
       /* eslint-enable camelcase */
     });
 
     // Tests nameLookup dot vs. bracket behavior.  Bracket is required in certain cases
     // to avoid errors in older browsers.
     it('should handle reserved words', function() {
-      shouldCompileTo('{{foo}} {{~null~}}', { foo: 'food' }, 'food');
+      expectTemplate('{{foo}} {{~null~}}')
+        .withInput({ foo: 'food' })
+        .toCompileTo('food');
     });
   });
   describe('#compilerInfo', function() {
@@ -49,7 +53,9 @@ describe('javascript-compiler api', function() {
           throw new Error("It didn't work");
         }
       };
-      shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food ');
+      expectTemplate('{{foo}} ')
+        .withInput({ foo: 'food' })
+        .toCompileTo('food ');
     });
   });
   describe('buffer', function() {
@@ -70,7 +76,9 @@ describe('javascript-compiler api', function() {
       handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = function() {
         return this.quotedString('foo_');
       };
-      shouldCompileTo('{{foo}} ', { foo: 'food' }, 'foo_food ');
+      expectTemplate('{{foo}} ')
+        .withInput({ foo: 'food' })
+        .toCompileTo('foo_food ');
     });
     it('should allow append buffer override', function() {
       handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(
@@ -78,7 +86,9 @@ describe('javascript-compiler api', function() {
       ) {
         return $superAppend.call(this, [string, ' + "_foo"']);
       };
-      shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo');
+      expectTemplate('{{foo}}')
+        .withInput({ foo: 'food' })
+        .toCompileTo('food_foo');
     });
   });
 
diff --git a/spec/partials.js b/spec/partials.js
index d00d4147..df092267 100644
--- a/spec/partials.js
+++ b/spec/partials.js
@@ -8,18 +8,18 @@ describe('partials', function() {
         { name: 'Alan', url: 'http://alan' }
       ]
     };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { dude: partial }],
-      true,
-      'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
-    );
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { dude: partial }, , false],
-      true,
-      'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
-    );
+
+    expectTemplate(string)
+      .withInput(hash)
+      .withPartials({ dude: partial })
+      .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
+
+    expectTemplate(string)
+      .withInput(hash)
+      .withPartials({ dude: partial })
+      .withRuntimeOptions({ data: false })
+      .withCompileOptions({ data: false })
+      .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
   });
 
   it('dynamic partials', function() {
@@ -36,63 +36,48 @@ describe('partials', function() {
         return 'dude';
       }
     };
-    shouldCompileToWithPartials(
-      string,
-      [hash, helpers, { dude: partial }],
-      true,
-      'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
-    );
-    shouldCompileToWithPartials(
-      string,
-      [hash, helpers, { dude: partial }, , false],
-      true,
-      'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
-    );
+
+    expectTemplate(string)
+      .withInput(hash)
+      .withHelpers(helpers)
+      .withPartials({ dude: partial })
+      .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
+
+    expectTemplate(string)
+      .withInput(hash)
+      .withHelpers(helpers)
+      .withPartials({ dude: partial })
+      .withRuntimeOptions({ data: false })
+      .withCompileOptions({ data: false })
+      .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
   });
+
   it('failing dynamic partials', function() {
-    var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
-    var partial = '{{name}} ({{url}}) ';
-    var hash = {
-      dudes: [
-        { name: 'Yehuda', url: 'http://yehuda' },
-        { name: 'Alan', url: 'http://alan' }
-      ]
-    };
-    var helpers = {
-      partial: function() {
+    expectTemplate('Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}')
+      .withInput({
+        dudes: [
+          { name: 'Yehuda', url: 'http://yehuda' },
+          { name: 'Alan', url: 'http://alan' }
+        ]
+      })
+      .withHelper('partial', function() {
         return 'missing';
-      }
-    };
-    shouldThrow(
-      function() {
-        shouldCompileToWithPartials(
-          string,
-          [hash, helpers, { dude: partial }],
-          true,
-          'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
-        );
-      },
-      Handlebars.Exception,
-      'The partial missing could not be found'
-    );
+      })
+      .withPartial('dude', '{{name}} ({{url}}) ')
+      .toThrow(Handlebars.Exception, 'The partial missing could not be found');
   });
 
   it('partials with context', function() {
-    var string = 'Dudes: {{>dude dudes}}';
-    var partial = '{{#this}}{{name}} ({{url}}) {{/this}}';
-    var hash = {
-      dudes: [
-        { name: 'Yehuda', url: 'http://yehuda' },
-        { name: 'Alan', url: 'http://alan' }
-      ]
-    };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { dude: partial }],
-      true,
-      'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
-      'Partials can be passed a context'
-    );
+    expectTemplate('Dudes: {{>dude dudes}}')
+      .withInput({
+        dudes: [
+          { name: 'Yehuda', url: 'http://yehuda' },
+          { name: 'Alan', url: 'http://alan' }
+        ]
+      })
+      .withPartial('dude', '{{#this}}{{name}} ({{url}}) {{/this}}')
+      .withMessage('Partials can be passed a context')
+      .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
   });
 
   it('partials with no context', function() {
@@ -103,98 +88,73 @@ describe('partials', function() {
         { name: 'Alan', url: 'http://alan' }
       ]
     };
-    shouldCompileToWithPartials(
-      'Dudes: {{#dudes}}{{>dude}}{{/dudes}}',
-      [hash, {}, { dude: partial }, { explicitPartialContext: true }],
-      true,
-      'Dudes:  ()  () '
-    );
-    shouldCompileToWithPartials(
-      'Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}',
-      [hash, {}, { dude: partial }, { explicitPartialContext: true }],
-      true,
-      'Dudes: foo () foo () '
-    );
+
+    expectTemplate('Dudes: {{#dudes}}{{>dude}}{{/dudes}}')
+      .withInput(hash)
+      .withPartial('dude', partial)
+      .withCompileOptions({ explicitPartialContext: true })
+      .toCompileTo('Dudes:  ()  () ');
+
+    expectTemplate('Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}')
+      .withInput(hash)
+      .withPartial('dude', partial)
+      .withCompileOptions({ explicitPartialContext: true })
+      .toCompileTo('Dudes: foo () foo () ');
   });
 
   it('partials with string context', function() {
-    var string = 'Dudes: {{>dude "dudes"}}';
-    var partial = '{{.}}';
-    var hash = {};
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { dude: partial }],
-      true,
-      'Dudes: dudes'
-    );
+    expectTemplate('Dudes: {{>dude "dudes"}}')
+      .withPartial('dude', '{{.}}')
+      .toCompileTo('Dudes: dudes');
   });
 
   it('partials with undefined context', function() {
-    var string = 'Dudes: {{>dude dudes}}';
-    var partial = '{{foo}} Empty';
-    var hash = {};
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { dude: partial }],
-      true,
-      'Dudes:  Empty'
-    );
+    expectTemplate('Dudes: {{>dude dudes}}')
+      .withPartial('dude', '{{foo}} Empty')
+      .toCompileTo('Dudes:  Empty');
   });
 
   it('partials with duplicate parameters', function() {
-    shouldThrow(
-      function() {
-        CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}');
-      },
+    expectTemplate('Dudes: {{>dude dudes foo bar=baz}}').toThrow(
       Error,
       'Unsupported number of partial arguments: 2 - 1:7'
     );
   });
 
   it('partials with parameters', function() {
-    var string = 'Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}';
-    var partial = '{{others.foo}}{{name}} ({{url}}) ';
-    var hash = {
-      foo: 'bar',
-      dudes: [
-        { name: 'Yehuda', url: 'http://yehuda' },
-        { name: 'Alan', url: 'http://alan' }
-      ]
-    };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { dude: partial }],
-      true,
-      'Dudes: barYehuda (http://yehuda) barAlan (http://alan) ',
-      'Basic partials output based on current context.'
-    );
+    expectTemplate('Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}')
+      .withInput({
+        foo: 'bar',
+        dudes: [
+          { name: 'Yehuda', url: 'http://yehuda' },
+          { name: 'Alan', url: 'http://alan' }
+        ]
+      })
+      .withPartial('dude', '{{others.foo}}{{name}} ({{url}}) ')
+      .withMessage('Basic partials output based on current context.')
+      .toCompileTo('Dudes: barYehuda (http://yehuda) barAlan (http://alan) ');
   });
 
   it('partial in a partial', function() {
-    var string = 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}';
-    var dude = '{{name}} {{> url}} ';
-    var url = '<a href="{{url}}">{{url}}</a>';
-    var hash = {
-      dudes: [
-        { name: 'Yehuda', url: 'http://yehuda' },
-        { name: 'Alan', url: 'http://alan' }
-      ]
-    };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { dude: dude, url: url }],
-      true,
-      'Dudes: Yehuda <a href="http://yehuda">http://yehuda</a> Alan <a href="http://alan">http://alan</a> ',
-      'Partials are rendered inside of other partials'
-    );
+    expectTemplate('Dudes: {{#dudes}}{{>dude}}{{/dudes}}')
+      .withInput({
+        dudes: [
+          { name: 'Yehuda', url: 'http://yehuda' },
+          { name: 'Alan', url: 'http://alan' }
+        ]
+      })
+      .withPartials({
+        dude: '{{name}} {{> url}} ',
+        url: '<a href="{{url}}">{{url}}</a>'
+      })
+      .withMessage('Partials are rendered inside of other partials')
+      .toCompileTo(
+        'Dudes: Yehuda <a href="http://yehuda">http://yehuda</a> Alan <a href="http://alan">http://alan</a> '
+      );
   });
 
   it('rendering undefined partial throws an exception', function() {
-    shouldThrow(
-      function() {
-        var template = CompilerContext.compile('{{> whatever}}');
-        template();
-      },
+    expectTemplate('{{> whatever}}').toThrow(
       Handlebars.Exception,
       'The partial whatever could not be found'
     );
@@ -212,87 +172,60 @@ describe('partials', function() {
   });
 
   it('rendering template partial in vm mode throws an exception', function() {
-    shouldThrow(
-      function() {
-        var template = CompilerContext.compile('{{> whatever}}');
-        template();
-      },
+    expectTemplate('{{> whatever}}').toThrow(
       Handlebars.Exception,
       'The partial whatever could not be found'
     );
   });
 
   it('rendering function partial in vm mode', function() {
-    var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
     function partial(context) {
       return context.name + ' (' + context.url + ') ';
     }
-    var hash = {
-      dudes: [
-        { name: 'Yehuda', url: 'http://yehuda' },
-        { name: 'Alan', url: 'http://alan' }
-      ]
-    };
-    shouldCompileTo(
-      string,
-      [hash, {}, { dude: partial }],
-      'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
-      'Function partials output based in VM.'
-    );
+    expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}')
+      .withInput({
+        dudes: [
+          { name: 'Yehuda', url: 'http://yehuda' },
+          { name: 'Alan', url: 'http://alan' }
+        ]
+      })
+      .withPartial('dude', partial)
+      .withMessage('Function partials output based in VM.')
+      .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
   });
 
   it('GH-14: a partial preceding a selector', function() {
-    var string = 'Dudes: {{>dude}} {{anotherDude}}';
-    var dude = '{{name}}';
-    var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { dude: dude }],
-      true,
-      'Dudes: Jeepers Creepers',
-      'Regular selectors can follow a partial'
-    );
+    expectTemplate('Dudes: {{>dude}} {{anotherDude}}')
+      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' })
+      .withPartial('dude', '{{name}}')
+      .withMessage('Regular selectors can follow a partial')
+      .toCompileTo('Dudes: Jeepers Creepers');
   });
 
   it('Partials with slash paths', function() {
-    var string = 'Dudes: {{> shared/dude}}';
-    var dude = '{{name}}';
-    var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { 'shared/dude': dude }],
-      true,
-      'Dudes: Jeepers',
-      'Partials can use literal paths'
-    );
+    expectTemplate('Dudes: {{> shared/dude}}')
+      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' })
+      .withPartial('shared/dude', '{{name}}')
+      .withMessage('Partials can use literal paths')
+      .toCompileTo('Dudes: Jeepers');
   });
 
   it('Partials with slash and point paths', function() {
-    var string = 'Dudes: {{> shared/dude.thing}}';
-    var dude = '{{name}}';
-    var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { 'shared/dude.thing': dude }],
-      true,
-      'Dudes: Jeepers',
-      'Partials can use literal with points in paths'
-    );
+    expectTemplate('Dudes: {{> shared/dude.thing}}')
+      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' })
+      .withPartial('shared/dude.thing', '{{name}}')
+      .withMessage('Partials can use literal with points in paths')
+      .toCompileTo('Dudes: Jeepers');
   });
 
   it('Global Partials', function() {
     handlebarsEnv.registerPartial('globalTest', '{{anotherDude}}');
 
-    var string = 'Dudes: {{> shared/dude}} {{> globalTest}}';
-    var dude = '{{name}}';
-    var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { 'shared/dude': dude }],
-      true,
-      'Dudes: Jeepers Creepers',
-      'Partials can use globals or passed'
-    );
+    expectTemplate('Dudes: {{> shared/dude}} {{> globalTest}}')
+      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' })
+      .withPartial('shared/dude', '{{name}}')
+      .withMessage('Partials can use globals or passed')
+      .toCompileTo('Dudes: Jeepers Creepers');
 
     handlebarsEnv.unregisterPartial('globalTest');
     equals(handlebarsEnv.partials.globalTest, undefined);
@@ -304,408 +237,317 @@ describe('partials', function() {
       globalTest: '{{anotherDude}}'
     });
 
-    var string = 'Dudes: {{> shared/dude}} {{> globalTest}}';
-    var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
-    shouldCompileToWithPartials(
-      string,
-      [hash],
-      true,
-      'Dudes: Jeepers Creepers',
-      'Partials can use globals or passed'
-    );
+    expectTemplate('Dudes: {{> shared/dude}} {{> globalTest}}')
+      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' })
+      .withPartial('notused', 'notused') // trick the test bench into running with partials enabled
+      .withMessage('Partials can use globals or passed')
+      .toCompileTo('Dudes: Jeepers Creepers');
   });
 
   it('Partials with integer path', function() {
-    var string = 'Dudes: {{> 404}}';
-    var dude = '{{name}}';
-    var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { 404: dude }],
-      true,
-      'Dudes: Jeepers',
-      'Partials can use literal paths'
-    );
+    expectTemplate('Dudes: {{> 404}}')
+      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' })
+      .withPartial(404, '{{name}}')
+      .withMessage('Partials can use literal paths')
+      .toCompileTo('Dudes: Jeepers');
   });
 
   it('Partials with complex path', function() {
-    var string = 'Dudes: {{> 404/asdf?.bar}}';
-    var dude = '{{name}}';
-    var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { '404/asdf?.bar': dude }],
-      true,
-      'Dudes: Jeepers',
-      'Partials can use literal paths'
-    );
+    expectTemplate('Dudes: {{> 404/asdf?.bar}}')
+      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' })
+      .withPartial('404/asdf?.bar', '{{name}}')
+      .withMessage('Partials can use literal paths')
+      .toCompileTo('Dudes: Jeepers');
   });
 
   it('Partials with escaped', function() {
-    var string = 'Dudes: {{> [+404/asdf?.bar]}}';
-    var dude = '{{name}}';
-    var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { '+404/asdf?.bar': dude }],
-      true,
-      'Dudes: Jeepers',
-      'Partials can use literal paths'
-    );
+    expectTemplate('Dudes: {{> [+404/asdf?.bar]}}')
+      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' })
+      .withPartial('+404/asdf?.bar', '{{name}}')
+      .withMessage('Partials can use literal paths')
+      .toCompileTo('Dudes: Jeepers');
   });
 
   it('Partials with string', function() {
-    var string = "Dudes: {{> '+404/asdf?.bar'}}";
-    var dude = '{{name}}';
-    var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { '+404/asdf?.bar': dude }],
-      true,
-      'Dudes: Jeepers',
-      'Partials can use literal paths'
-    );
+    expectTemplate("Dudes: {{> '+404/asdf?.bar'}}")
+      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' })
+      .withPartial('+404/asdf?.bar', '{{name}}')
+      .withMessage('Partials can use literal paths')
+      .toCompileTo('Dudes: Jeepers');
   });
 
   it('should handle empty partial', function() {
-    var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
-    var partial = '';
-    var hash = {
-      dudes: [
-        { name: 'Yehuda', url: 'http://yehuda' },
-        { name: 'Alan', url: 'http://alan' }
-      ]
-    };
-    shouldCompileToWithPartials(
-      string,
-      [hash, {}, { dude: partial }],
-      true,
-      'Dudes: '
-    );
+    expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}')
+      .withInput({
+        dudes: [
+          { name: 'Yehuda', url: 'http://yehuda' },
+          { name: 'Alan', url: 'http://alan' }
+        ]
+      })
+      .withPartial('dude', '')
+      .toCompileTo('Dudes: ');
   });
 
   it('throw on missing partial', function() {
     var compile = handlebarsEnv.compile;
+    var compileWithPartial = CompilerContext.compileWithPartial;
     handlebarsEnv.compile = undefined;
-    shouldThrow(
-      function() {
-        shouldCompileTo('{{> dude}}', [{}, {}, { dude: 'fail' }], '');
-      },
-      Error,
-      /The partial dude could not be compiled/
-    );
+    CompilerContext.compileWithPartial = CompilerContext.compile;
+    expectTemplate('{{> dude}}')
+      .withPartials({ dude: 'fail' })
+      .toThrow(Error, /The partial dude could not be compiled/);
     handlebarsEnv.compile = compile;
+    CompilerContext.compileWithPartial = compileWithPartial;
   });
 
   describe('partial blocks', function() {
     it('should render partial block as default', function() {
-      shouldCompileToWithPartials(
-        '{{#> dude}}success{{/dude}}',
-        [{}, {}, {}],
-        true,
-        'success'
-      );
+      expectTemplate('{{#> dude}}success{{/dude}}').toCompileTo('success');
     });
+
     it('should execute default block with proper context', function() {
-      shouldCompileToWithPartials(
-        '{{#> dude context}}{{value}}{{/dude}}',
-        [{ context: { value: 'success' } }, {}, {}],
-        true,
-        'success'
-      );
+      expectTemplate('{{#> dude context}}{{value}}{{/dude}}')
+        .withInput({ context: { value: 'success' } })
+        .toCompileTo('success');
     });
+
     it('should propagate block parameters to default block', function() {
-      shouldCompileToWithPartials(
-        '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
-        [{ context: { value: 'success' } }, {}, {}],
-        true,
-        'success'
-      );
+      expectTemplate(
+        '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}'
+      )
+        .withInput({ context: { value: 'success' } })
+        .toCompileTo('success');
     });
 
     it('should not use partial block if partial exists', function() {
-      shouldCompileToWithPartials(
-        '{{#> dude}}fail{{/dude}}',
-        [{}, {}, { dude: 'success' }],
-        true,
-        'success'
-      );
+      expectTemplate('{{#> dude}}fail{{/dude}}')
+        .withPartials({ dude: 'success' })
+        .toCompileTo('success');
     });
 
     it('should render block from partial', function() {
-      shouldCompileToWithPartials(
-        '{{#> dude}}success{{/dude}}',
-        [{}, {}, { dude: '{{> @partial-block }}' }],
-        true,
-        'success'
-      );
+      expectTemplate('{{#> dude}}success{{/dude}}')
+        .withPartials({ dude: '{{> @partial-block }}' })
+        .toCompileTo('success');
     });
+
     it('should be able to render the partial-block twice', function() {
-      shouldCompileToWithPartials(
-        '{{#> dude}}success{{/dude}}',
-        [{}, {}, { dude: '{{> @partial-block }} {{> @partial-block }}' }],
-        true,
-        'success success'
-      );
+      expectTemplate('{{#> dude}}success{{/dude}}')
+        .withPartials({ dude: '{{> @partial-block }} {{> @partial-block }}' })
+        .toCompileTo('success success');
     });
+
     it('should render block from partial with context', function() {
-      shouldCompileToWithPartials(
-        '{{#> dude}}{{value}}{{/dude}}',
-        [
-          { context: { value: 'success' } },
-          {},
-          { dude: '{{#with context}}{{> @partial-block }}{{/with}}' }
-        ],
-        true,
-        'success'
-      );
+      expectTemplate('{{#> dude}}{{value}}{{/dude}}')
+        .withInput({ context: { value: 'success' } })
+        .withPartials({
+          dude: '{{#with context}}{{> @partial-block }}{{/with}}'
+        })
+        .toCompileTo('success');
     });
 
     it('should be able to access the @data frame from a partial-block', function() {
-      shouldCompileToWithPartials(
-        '{{#> dude}}in-block: {{@root/value}}{{/dude}}',
-        [
-          { value: 'success' },
-          {},
-          {
-            dude:
-              '<code>before-block: {{@root/value}} {{>   @partial-block }}</code>'
-          }
-        ],
-        true,
-        '<code>before-block: success in-block: success</code>'
-      );
+      expectTemplate('{{#> dude}}in-block: {{@root/value}}{{/dude}}')
+        .withInput({ value: 'success' })
+        .withPartials({
+          dude:
+            '<code>before-block: {{@root/value}} {{>   @partial-block }}</code>'
+        })
+        .toCompileTo('<code>before-block: success in-block: success</code>');
     });
 
     it('should allow the #each-helper to be used along with partial-blocks', function() {
-      shouldCompileToWithPartials(
-        '<template>{{#> list value}}value = {{.}}{{/list}}</template>',
-        [
-          { value: ['a', 'b', 'c'] },
-          {},
-          {
-            list:
-              '<list>{{#each .}}<item>{{> @partial-block}}</item>{{/each}}</list>'
-          }
-        ],
-        true,
-        '<template><list><item>value = a</item><item>value = b</item><item>value = c</item></list></template>'
-      );
+      expectTemplate(
+        '<template>{{#> list value}}value = {{.}}{{/list}}</template>'
+      )
+        .withInput({
+          value: ['a', 'b', 'c']
+        })
+        .withPartials({
+          list:
+            '<list>{{#each .}}<item>{{> @partial-block}}</item>{{/each}}</list>'
+        })
+        .toCompileTo(
+          '<template><list><item>value = a</item><item>value = b</item><item>value = c</item></list></template>'
+        );
     });
+
     it('should render block from partial with context (twice)', function() {
-      shouldCompileToWithPartials(
-        '{{#> dude}}{{value}}{{/dude}}',
-        [
-          { context: { value: 'success' } },
-          {},
-          {
-            dude:
-              '{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}'
-          }
-        ],
-        true,
-        'success success'
-      );
+      expectTemplate('{{#> dude}}{{value}}{{/dude}}')
+        .withInput({ context: { value: 'success' } })
+        .withPartials({
+          dude:
+            '{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}'
+        })
+        .toCompileTo('success success');
     });
+
     it('should render block from partial with context', function() {
-      shouldCompileToWithPartials(
-        '{{#> dude}}{{../context/value}}{{/dude}}',
-        [
-          { context: { value: 'success' } },
-          {},
-          { dude: '{{#with context}}{{> @partial-block }}{{/with}}' }
-        ],
-        true,
-        'success'
-      );
+      expectTemplate('{{#> dude}}{{../context/value}}{{/dude}}')
+        .withInput({ context: { value: 'success' } })
+        .withPartials({
+          dude: '{{#with context}}{{> @partial-block }}{{/with}}'
+        })
+        .toCompileTo('success');
     });
+
     it('should render block from partial with block params', function() {
-      shouldCompileToWithPartials(
-        '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
-        [
-          { context: { value: 'success' } },
-          {},
-          { dude: '{{> @partial-block }}' }
-        ],
-        true,
-        'success'
-      );
+      expectTemplate(
+        '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}'
+      )
+        .withInput({ context: { value: 'success' } })
+        .withPartials({ dude: '{{> @partial-block }}' })
+        .toCompileTo('success');
     });
+
     it('should render nested partial blocks', function() {
-      shouldCompileToWithPartials(
-        '<template>{{#> outer}}{{value}}{{/outer}}</template>',
-        [
-          { value: 'success' },
-          {},
-          {
-            outer:
-              '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}</outer>',
-            nested: '<nested>{{> @partial-block}}</nested>'
-          }
-        ],
-        true,
-        '<template><outer><nested><outer-block>success</outer-block></nested></outer></template>'
-      );
+      expectTemplate('<template>{{#> outer}}{{value}}{{/outer}}</template>')
+        .withInput({ value: 'success' })
+        .withPartials({
+          outer:
+            '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}</outer>',
+          nested: '<nested>{{> @partial-block}}</nested>'
+        })
+        .toCompileTo(
+          '<template><outer><nested><outer-block>success</outer-block></nested></outer></template>'
+        );
     });
+
     it('should render nested partial blocks at different nesting levels', function() {
-      shouldCompileToWithPartials(
-        '<template>{{#> outer}}{{value}}{{/outer}}</template>',
-        [
-          { value: 'success' },
-          {},
-          {
-            outer:
-              '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}</outer>',
-            nested: '<nested>{{> @partial-block}}</nested>'
-          }
-        ],
-        true,
-        '<template><outer><nested><outer-block>success</outer-block></nested>success</outer></template>'
-      );
+      expectTemplate('<template>{{#> outer}}{{value}}{{/outer}}</template>')
+        .withInput({ value: 'success' })
+        .withPartials({
+          outer:
+            '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}</outer>',
+          nested: '<nested>{{> @partial-block}}</nested>'
+        })
+        .toCompileTo(
+          '<template><outer><nested><outer-block>success</outer-block></nested>success</outer></template>'
+        );
     });
+
     it('should render nested partial blocks at different nesting levels (twice)', function() {
-      shouldCompileToWithPartials(
-        '<template>{{#> outer}}{{value}}{{/outer}}</template>',
-        [
-          { value: 'success' },
-          {},
-          {
-            outer:
-              '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}+{{> @partial-block}}</outer>',
-            nested: '<nested>{{> @partial-block}}</nested>'
-          }
-        ],
-        true,
-        '<template><outer><nested><outer-block>success success</outer-block></nested>success+success</outer></template>'
-      );
+      expectTemplate('<template>{{#> outer}}{{value}}{{/outer}}</template>')
+        .withInput({ value: 'success' })
+        .withPartials({
+          outer:
+            '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}+{{> @partial-block}}</outer>',
+          nested: '<nested>{{> @partial-block}}</nested>'
+        })
+        .toCompileTo(
+          '<template><outer><nested><outer-block>success success</outer-block></nested>success+success</outer></template>'
+        );
     });
+
     it('should render nested partial blocks (twice at each level)', function() {
-      shouldCompileToWithPartials(
-        '<template>{{#> outer}}{{value}}{{/outer}}</template>',
-        [
-          { value: 'success' },
-          {},
-          {
-            outer:
-              '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}</outer>',
-            nested: '<nested>{{> @partial-block}}{{> @partial-block}}</nested>'
-          }
-        ],
-        true,
-        '<template><outer>' +
-          '<nested><outer-block>success success</outer-block><outer-block>success success</outer-block></nested>' +
-          '</outer></template>'
-      );
+      expectTemplate('<template>{{#> outer}}{{value}}{{/outer}}</template>')
+        .withInput({ value: 'success' })
+        .withPartials({
+          outer:
+            '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}</outer>',
+          nested: '<nested>{{> @partial-block}}{{> @partial-block}}</nested>'
+        })
+        .toCompileTo(
+          '<template><outer>' +
+            '<nested><outer-block>success success</outer-block><outer-block>success success</outer-block></nested>' +
+            '</outer></template>'
+        );
     });
   });
 
   describe('inline partials', function() {
     it('should define inline partials for template', function() {
-      shouldCompileTo(
-        '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
-        {},
-        'success'
-      );
+      expectTemplate(
+        '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}'
+      ).toCompileTo('success');
     });
+
     it('should overwrite multiple partials in the same template', function() {
-      shouldCompileTo(
-        '{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
-        {},
-        'success'
-      );
+      expectTemplate(
+        '{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}'
+      ).toCompileTo('success');
     });
+
     it('should define inline partials for block', function() {
-      shouldCompileTo(
-        '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}',
-        {},
-        'success'
-      );
-      shouldThrow(
-        function() {
-          shouldCompileTo(
-            '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}',
-            {},
-            'success'
-          );
-        },
-        Error,
-        /myPartial could not/
-      );
+      expectTemplate(
+        '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}'
+      ).toCompileTo('success');
+
+      expectTemplate(
+        '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}'
+      ).toThrow(Error, /myPartial could not/);
     });
+
     it('should override global partials', function() {
-      shouldCompileTo(
-        '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
-        {
-          hash: {},
-          partials: {
-            myPartial: function() {
-              return 'fail';
-            }
+      expectTemplate(
+        '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}'
+      )
+        .withPartials({
+          myPartial: function() {
+            return 'fail';
           }
-        },
-        'success'
-      );
+        })
+        .toCompileTo('success');
     });
+
     it('should override template partials', function() {
-      shouldCompileTo(
-        '{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}',
-        {},
-        'success'
-      );
+      expectTemplate(
+        '{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}'
+      ).toCompileTo('success');
     });
+
     it('should override partials down the entire stack', function() {
-      shouldCompileTo(
-        '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}',
-        {},
-        'success'
-      );
+      expectTemplate(
+        '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}'
+      ).toCompileTo('success');
     });
 
     it('should define inline partials for partial call', function() {
-      shouldCompileToWithPartials(
-        '{{#*inline "myPartial"}}success{{/inline}}{{> dude}}',
-        [{}, {}, { dude: '{{> myPartial }}' }],
-        true,
-        'success'
-      );
+      expectTemplate('{{#*inline "myPartial"}}success{{/inline}}{{> dude}}')
+        .withPartials({ dude: '{{> myPartial }}' })
+        .toCompileTo('success');
     });
+
     it('should define inline partials in partial block call', function() {
-      shouldCompileToWithPartials(
-        '{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}',
-        [{}, {}, { dude: '{{> myPartial }}' }],
-        true,
-        'success'
-      );
+      expectTemplate(
+        '{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}'
+      )
+        .withPartials({ dude: '{{> myPartial }}' })
+        .toCompileTo('success');
     });
+
     it('should render nested inline partials', function() {
-      shouldCompileToWithPartials(
+      expectTemplate(
         '{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' +
           '{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
-          '{{#>outer}}{{value}}{{/outer}}',
-        [{ value: 'success' }, {}, {}],
-        true,
-        '<inner><outer-block>success</outer-block></inner>'
-      );
+          '{{#>outer}}{{value}}{{/outer}}'
+      )
+        .withInput({ value: 'success' })
+        .toCompileTo('<inner><outer-block>success</outer-block></inner>');
     });
+
     it('should render nested inline partials with partial-blocks on different nesting levels', function() {
-      shouldCompileToWithPartials(
+      expectTemplate(
         '{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{>@partial-block}}{{/inline}}' +
           '{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
-          '{{#>outer}}{{value}}{{/outer}}',
-        [{ value: 'success' }, {}, {}],
-        true,
-        '<inner><outer-block>success</outer-block></inner>success'
-      );
+          '{{#>outer}}{{value}}{{/outer}}'
+      )
+        .withInput({ value: 'success' })
+        .toCompileTo(
+          '<inner><outer-block>success</outer-block></inner>success'
+        );
     });
+
     it('should render nested inline partials (twice at each level)', function() {
-      shouldCompileToWithPartials(
+      expectTemplate(
         '{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}} {{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' +
           '{{#*inline "inner"}}<inner>{{>@partial-block}}{{>@partial-block}}</inner>{{/inline}}' +
-          '{{#>outer}}{{value}}{{/outer}}',
-        [{ value: 'success' }, {}, {}],
-        true,
-        '<inner><outer-block>success success</outer-block><outer-block>success success</outer-block></inner>'
-      );
+          '{{#>outer}}{{value}}{{/outer}}'
+      )
+        .withInput({ value: 'success' })
+        .toCompileTo(
+          '<inner><outer-block>success success</outer-block><outer-block>success success</outer-block></inner>'
+        );
     });
   });
 
@@ -720,125 +562,119 @@ describe('partials', function() {
 
   describe('standalone partials', function() {
     it('indented partials', function() {
-      var string = 'Dudes:\n{{#dudes}}\n  {{>dude}}\n{{/dudes}}';
-      var dude = '{{name}}\n';
-      var hash = {
-        dudes: [
-          { name: 'Yehuda', url: 'http://yehuda' },
-          { name: 'Alan', url: 'http://alan' }
-        ]
-      };
-      shouldCompileToWithPartials(
-        string,
-        [hash, {}, { dude: dude }],
-        true,
-        'Dudes:\n  Yehuda\n  Alan\n'
-      );
+      expectTemplate('Dudes:\n{{#dudes}}\n  {{>dude}}\n{{/dudes}}')
+        .withInput({
+          dudes: [
+            { name: 'Yehuda', url: 'http://yehuda' },
+            { name: 'Alan', url: 'http://alan' }
+          ]
+        })
+        .withPartial('dude', '{{name}}\n')
+        .toCompileTo('Dudes:\n  Yehuda\n  Alan\n');
     });
+
     it('nested indented partials', function() {
-      var string = 'Dudes:\n{{#dudes}}\n  {{>dude}}\n{{/dudes}}';
-      var dude = '{{name}}\n {{> url}}';
-      var url = '{{url}}!\n';
-      var hash = {
-        dudes: [
-          { name: 'Yehuda', url: 'http://yehuda' },
-          { name: 'Alan', url: 'http://alan' }
-        ]
-      };
-      shouldCompileToWithPartials(
-        string,
-        [hash, {}, { dude: dude, url: url }],
-        true,
-        'Dudes:\n  Yehuda\n   http://yehuda!\n  Alan\n   http://alan!\n'
-      );
+      expectTemplate('Dudes:\n{{#dudes}}\n  {{>dude}}\n{{/dudes}}')
+        .withInput({
+          dudes: [
+            { name: 'Yehuda', url: 'http://yehuda' },
+            { name: 'Alan', url: 'http://alan' }
+          ]
+        })
+        .withPartials({
+          dude: '{{name}}\n {{> url}}',
+          url: '{{url}}!\n'
+        })
+        .toCompileTo(
+          'Dudes:\n  Yehuda\n   http://yehuda!\n  Alan\n   http://alan!\n'
+        );
     });
+
     it('prevent nested indented partials', function() {
-      var string = 'Dudes:\n{{#dudes}}\n  {{>dude}}\n{{/dudes}}';
-      var dude = '{{name}}\n {{> url}}';
-      var url = '{{url}}!\n';
-      var hash = {
-        dudes: [
-          { name: 'Yehuda', url: 'http://yehuda' },
-          { name: 'Alan', url: 'http://alan' }
-        ]
-      };
-      shouldCompileToWithPartials(
-        string,
-        [hash, {}, { dude: dude, url: url }, { preventIndent: true }],
-        true,
-        'Dudes:\n  Yehuda\n http://yehuda!\n  Alan\n http://alan!\n'
-      );
+      expectTemplate('Dudes:\n{{#dudes}}\n  {{>dude}}\n{{/dudes}}')
+        .withInput({
+          dudes: [
+            { name: 'Yehuda', url: 'http://yehuda' },
+            { name: 'Alan', url: 'http://alan' }
+          ]
+        })
+        .withPartials({
+          dude: '{{name}}\n {{> url}}',
+          url: '{{url}}!\n'
+        })
+        .withCompileOptions({ preventIndent: true })
+        .toCompileTo(
+          'Dudes:\n  Yehuda\n http://yehuda!\n  Alan\n http://alan!\n'
+        );
     });
   });
 
   describe('compat mode', function() {
     it('partials can access parents', function() {
-      var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
-      var partial = '{{name}} ({{url}}) {{root}} ';
-      var hash = {
-        root: 'yes',
-        dudes: [
-          { name: 'Yehuda', url: 'http://yehuda' },
-          { name: 'Alan', url: 'http://alan' }
-        ]
-      };
-      shouldCompileToWithPartials(
-        string,
-        [hash, {}, { dude: partial }, true],
-        true,
-        'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
-      );
+      expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}')
+        .withInput({
+          root: 'yes',
+          dudes: [
+            { name: 'Yehuda', url: 'http://yehuda' },
+            { name: 'Alan', url: 'http://alan' }
+          ]
+        })
+        .withPartials({ dude: '{{name}} ({{url}}) {{root}} ' })
+        .withCompileOptions({ compat: true })
+        .toCompileTo(
+          'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
+        );
     });
+
     it('partials can access parents with custom context', function() {
-      var string = 'Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}';
-      var partial = '{{name}} ({{url}}) {{root}} ';
-      var hash = {
-        root: 'yes',
-        dudes: [
-          { name: 'Yehuda', url: 'http://yehuda' },
-          { name: 'Alan', url: 'http://alan' }
-        ]
-      };
-      shouldCompileToWithPartials(
-        string,
-        [hash, {}, { dude: partial }, true],
-        true,
-        'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
-      );
+      expectTemplate('Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}')
+        .withInput({
+          root: 'yes',
+          dudes: [
+            { name: 'Yehuda', url: 'http://yehuda' },
+            { name: 'Alan', url: 'http://alan' }
+          ]
+        })
+        .withPartials({ dude: '{{name}} ({{url}}) {{root}} ' })
+        .withCompileOptions({ compat: true })
+        .toCompileTo(
+          'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
+        );
     });
+
     it('partials can access parents without data', function() {
-      var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
-      var partial = '{{name}} ({{url}}) {{root}} ';
-      var hash = {
-        root: 'yes',
-        dudes: [
-          { name: 'Yehuda', url: 'http://yehuda' },
-          { name: 'Alan', url: 'http://alan' }
-        ]
-      };
-      shouldCompileToWithPartials(
-        string,
-        [hash, {}, { dude: partial }, true, false],
-        true,
-        'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
-      );
+      expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}')
+        .withInput({
+          root: 'yes',
+          dudes: [
+            { name: 'Yehuda', url: 'http://yehuda' },
+            { name: 'Alan', url: 'http://alan' }
+          ]
+        })
+        .withPartials({ dude: '{{name}} ({{url}}) {{root}} ' })
+        .withRuntimeOptions({ data: false })
+        .withCompileOptions({ data: false, compat: true })
+        .toCompileTo(
+          'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
+        );
     });
+
     it('partials inherit compat', function() {
-      var string = 'Dudes: {{> dude}}';
-      var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}';
-      var hash = {
-        root: 'yes',
-        dudes: [
-          { name: 'Yehuda', url: 'http://yehuda' },
-          { name: 'Alan', url: 'http://alan' }
-        ]
-      };
-      shouldCompileToWithPartials(
-        string,
-        [hash, {}, { dude: partial }, true],
-        true,
-        'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
-      );
+      expectTemplate('Dudes: {{> dude}}')
+        .withInput({
+          root: 'yes',
+          dudes: [
+            { name: 'Yehuda', url: 'http://yehuda' },
+            { name: 'Alan', url: 'http://alan' }
+          ]
+        })
+        .withPartials({
+          dude: '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}'
+        })
+        .withCompileOptions({ compat: true })
+        .toCompileTo(
+          'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
+        );
     });
   });
 });
diff --git a/spec/regressions.js b/spec/regressions.js
index f6147ad6..86d4634d 100644
--- a/spec/regressions.js
+++ b/spec/regressions.js
@@ -1,61 +1,53 @@
 describe('Regressions', function() {
   it('GH-94: Cannot read property of undefined', function() {
-    var data = {
-      books: [
-        {
-          title: 'The origin of species',
-          author: {
-            name: 'Charles Darwin'
+    expectTemplate('{{#books}}{{title}}{{author.name}}{{/books}}')
+      .withInput({
+        books: [
+          {
+            title: 'The origin of species',
+            author: {
+              name: 'Charles Darwin'
+            }
+          },
+          {
+            title: 'Lazarillo de Tormes'
           }
-        },
-        {
-          title: 'Lazarillo de Tormes'
-        }
-      ]
-    };
-    var string = '{{#books}}{{title}}{{author.name}}{{/books}}';
-    shouldCompileTo(
-      string,
-      data,
-      'The origin of speciesCharles DarwinLazarillo de Tormes',
-      'Renders without an undefined property error'
-    );
+        ]
+      })
+      .withMessage('Renders without an undefined property error')
+      .toCompileTo('The origin of speciesCharles DarwinLazarillo de Tormes');
   });
 
   it("GH-150: Inverted sections print when they shouldn't", function() {
     var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}';
 
-    shouldCompileTo(
-      string,
-      {},
-      'not set :: ',
-      "inverted sections run when property isn't present in context"
-    );
-    shouldCompileTo(
-      string,
-      { set: undefined },
-      'not set :: ',
-      'inverted sections run when property is undefined'
-    );
-    shouldCompileTo(
-      string,
-      { set: false },
-      'not set :: ',
-      'inverted sections run when property is false'
-    );
-    shouldCompileTo(
-      string,
-      { set: true },
-      ' :: set',
-      "inverted sections don't run when property is true"
-    );
+    expectTemplate(string)
+      .withMessage(
+        "inverted sections run when property isn't present in context"
+      )
+      .toCompileTo('not set :: ');
+
+    expectTemplate(string)
+      .withInput({ set: undefined })
+      .withMessage('inverted sections run when property is undefined')
+      .toCompileTo('not set :: ');
+
+    expectTemplate(string)
+      .withInput({ set: false })
+      .withMessage('inverted sections run when property is false')
+      .toCompileTo('not set :: ');
+
+    expectTemplate(string)
+      .withInput({ set: true })
+      .withMessage("inverted sections don't run when property is true")
+      .toCompileTo(' :: set');
   });
 
   it('GH-158: Using array index twice, breaks the template', function() {
-    var string = '{{arr.[0]}}, {{arr.[1]}}';
-    var data = { arr: [1, 2] };
-
-    shouldCompileTo(string, data, '1, 2', 'it works as expected');
+    expectTemplate('{{arr.[0]}}, {{arr.[1]}}')
+      .withInput({ arr: [1, 2] })
+      .withMessage('it works as expected')
+      .toCompileTo('1, 2');
   });
 
   it("bug reported by @fat where lambdas weren't being properly resolved", function() {
@@ -73,6 +65,7 @@ describe('Regressions', function() {
       '\n' +
       '<small>Nothing to check out...</small>\n' +
       '{{/hasThings}}';
+
     var data = {
       thing: function() {
         return 'blah';
@@ -95,25 +88,22 @@ describe('Regressions', function() {
       '<li class=two>@dhg</li>\n' +
       '<li class=three>@sayrer</li>\n' +
       '</ul>.\n';
-    shouldCompileTo(string, data, output);
+
+    expectTemplate(string)
+      .withInput(data)
+      .toCompileTo(output);
   });
 
   it('GH-408: Multiple loops fail', function() {
-    var context = [
-      { name: 'John Doe', location: { city: 'Chicago' } },
-      { name: 'Jane Doe', location: { city: 'New York' } }
-    ];
-
-    var template = CompilerContext.compile(
+    expectTemplate(
       '{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}'
-    );
-
-    var result = template(context);
-    equals(
-      result,
-      'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe',
-      'It should output multiple times'
-    );
+    )
+      .withInput([
+        { name: 'John Doe', location: { city: 'Chicago' } },
+        { name: 'Jane Doe', location: { city: 'New York' } }
+      ])
+      .withMessage('It should output multiple times')
+      .toCompileTo('John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe');
   });
 
   it('GS-428: Nested if else rendering', function() {
@@ -131,259 +121,242 @@ describe('Regressions', function() {
       }
     };
 
-    shouldCompileTo(succeedingTemplate, [{}, helpers], '   Expected  ');
-    shouldCompileTo(failingTemplate, [{}, helpers], '  Expected  ');
+    expectTemplate(succeedingTemplate)
+      .withHelpers(helpers)
+      .toCompileTo('   Expected  ');
+
+    expectTemplate(failingTemplate)
+      .withHelpers(helpers)
+      .toCompileTo('  Expected  ');
   });
 
   it('GH-458: Scoped this identifier', function() {
-    shouldCompileTo('{{./foo}}', { foo: 'bar' }, 'bar');
+    expectTemplate('{{./foo}}')
+      .withInput({ foo: 'bar' })
+      .toCompileTo('bar');
   });
 
   it('GH-375: Unicode line terminators', function() {
-    shouldCompileTo('\u2028', {}, '\u2028');
+    expectTemplate('\u2028').toCompileTo('\u2028');
   });
 
   it('GH-534: Object prototype aliases', function() {
     /* eslint-disable no-extend-native */
     Object.prototype[0xd834] = true;
 
-    shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
+    expectTemplate('{{foo}}')
+      .withInput({ foo: 'bar' })
+      .toCompileTo('bar');
 
     delete Object.prototype[0xd834];
     /* eslint-enable no-extend-native */
   });
 
   it('GH-437: Matching escaping', function() {
-    shouldThrow(function() {
-      CompilerContext.compile('{{{a}}');
-    }, Error);
-    shouldThrow(function() {
-      CompilerContext.compile('{{a}}}');
-    }, Error);
+    expectTemplate('{{{a}}').toThrow(Error, /Parse error on/);
+    expectTemplate('{{a}}}').toThrow(Error, /Parse error on/);
   });
 
   it('GH-676: Using array in escaping mustache fails', function() {
-    var string = '{{arr}}';
     var data = { arr: [1, 2] };
 
-    shouldCompileTo(string, data, data.arr.toString(), 'it works as expected');
+    expectTemplate('{{arr}}')
+      .withInput(data)
+      .withMessage('it works as expected')
+      .toCompileTo(data.arr.toString());
   });
 
   it('Mustache man page', function() {
-    var string =
-      'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}';
-    var data = {
-      name: 'Chris',
-      value: 10000,
-      taxed_value: 10000 - 10000 * 0.4,
-      in_ca: true
-    };
-
-    shouldCompileTo(
-      string,
-      data,
-      'Hello Chris. You have just won $10000! Well, $6000, after taxes.',
-      'the hello world mustache example works'
-    );
+    expectTemplate(
+      'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}'
+    )
+      .withInput({
+        name: 'Chris',
+        value: 10000,
+        taxed_value: 10000 - 10000 * 0.4,
+        in_ca: true
+      })
+      .withMessage('the hello world mustache example works')
+      .toCompileTo(
+        'Hello Chris. You have just won $10000! Well, $6000, after taxes.'
+      );
   });
 
   it('GH-731: zero context rendering', function() {
-    shouldCompileTo(
-      '{{#foo}} This is {{bar}} ~ {{/foo}}',
-      { foo: 0, bar: 'OK' },
-      ' This is  ~ '
-    );
+    expectTemplate('{{#foo}} This is {{bar}} ~ {{/foo}}')
+      .withInput({
+        foo: 0,
+        bar: 'OK'
+      })
+      .toCompileTo(' This is  ~ ');
   });
 
   it('GH-820: zero pathed rendering', function() {
-    shouldCompileTo('{{foo.bar}}', { foo: 0 }, '');
+    expectTemplate('{{foo.bar}}')
+      .withInput({ foo: 0 })
+      .toCompileTo('');
   });
 
   it('GH-837: undefined values for helpers', function() {
-    var helpers = {
-      str: function(value) {
-        return value + '';
-      }
-    };
-
-    shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined');
+    expectTemplate('{{str bar.baz}}')
+      .withHelpers({
+        str: function(value) {
+          return value + '';
+        }
+      })
+      .toCompileTo('undefined');
   });
 
   it('GH-926: Depths and de-dupe', function() {
-    var context = {
-      name: 'foo',
-      data: [1],
-      notData: [1]
-    };
-
-    var template = CompilerContext.compile(
+    expectTemplate(
       '{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}'
-    );
-
-    var result = template(context);
-    equals(result, 'foo');
+    )
+      .withInput({
+        name: 'foo',
+        data: [1],
+        notData: [1]
+      })
+      .toCompileTo('foo');
   });
 
   it('GH-1021: Each empty string key', function() {
-    var data = {
-      '': 'foo',
-      name: 'Chris',
-      value: 10000
-    };
-
-    shouldCompileTo(
-      '{{#each data}}Key: {{@key}}\n{{/each}}',
-      { data: data },
-      'Key: \nKey: name\nKey: value\n'
-    );
+    expectTemplate('{{#each data}}Key: {{@key}}\n{{/each}}')
+      .withInput({
+        data: {
+          '': 'foo',
+          name: 'Chris',
+          value: 10000
+        }
+      })
+      .toCompileTo('Key: \nKey: name\nKey: value\n');
   });
 
   it('GH-1054: Should handle simple safe string responses', function() {
-    var root = '{{#wrap}}{{>partial}}{{/wrap}}';
-    var partials = {
-      partial: '{{#wrap}}<partial>{{/wrap}}'
-    };
-    var helpers = {
-      wrap: function(options) {
-        return new Handlebars.SafeString(options.fn());
-      }
-    };
-
-    shouldCompileToWithPartials(
-      root,
-      [{}, helpers, partials],
-      true,
-      '<partial>'
-    );
+    expectTemplate('{{#wrap}}{{>partial}}{{/wrap}}')
+      .withHelpers({
+        wrap: function(options) {
+          return new Handlebars.SafeString(options.fn());
+        }
+      })
+      .withPartials({
+        partial: '{{#wrap}}<partial>{{/wrap}}'
+      })
+      .toCompileTo('<partial>');
   });
 
   it('GH-1065: Sparse arrays', function() {
     var array = [];
     array[1] = 'foo';
     array[3] = 'bar';
-    shouldCompileTo(
-      '{{#each array}}{{@index}}{{.}}{{/each}}',
-      { array: array },
-      '1foo3bar'
-    );
+    expectTemplate('{{#each array}}{{@index}}{{.}}{{/each}}')
+      .withInput({ array: array })
+      .toCompileTo('1foo3bar');
   });
 
   it('GH-1093: Undefined helper context', function() {
-    var obj = { foo: undefined, bar: 'bat' };
-    var helpers = {
-      helper: function() {
-        // It's valid to execute a block against an undefined context, but
-        // helpers can not do so, so we expect to have an empty object here;
-        for (var name in this) {
-          if (Object.prototype.hasOwnProperty.call(this, name)) {
-            return 'found';
+    expectTemplate('{{#each obj}}{{{helper}}}{{.}}{{/each}}')
+      .withInput({ obj: { foo: undefined, bar: 'bat' } })
+      .withHelpers({
+        helper: function() {
+          // It's valid to execute a block against an undefined context, but
+          // helpers can not do so, so we expect to have an empty object here;
+          for (var name in this) {
+            if (Object.prototype.hasOwnProperty.call(this, name)) {
+              return 'found';
+            }
           }
+          // And to make IE happy, check for the known string as length is not enumerated.
+          return this === 'bat' ? 'found' : 'not';
         }
-        // And to make IE happy, check for the known string as length is not enumerated.
-        return this === 'bat' ? 'found' : 'not';
-      }
-    };
-
-    shouldCompileTo(
-      '{{#each obj}}{{{helper}}}{{.}}{{/each}}',
-      [{ obj: obj }, helpers],
-      'notfoundbat'
-    );
+      })
+      .toCompileTo('notfoundbat');
   });
 
   it('should support multiple levels of inline partials', function() {
-    var string =
-      '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}';
-    var partials = {
-      doctype: 'doctype{{> content}}',
-      layout:
-        '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
-    };
-    shouldCompileToWithPartials(
-      string,
-      [{}, {}, partials],
-      true,
-      'doctypelayoutsubcontent'
-    );
+    expectTemplate(
+      '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}'
+    )
+      .withPartials({
+        doctype: 'doctype{{> content}}',
+        layout:
+          '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
+      })
+      .toCompileTo('doctypelayoutsubcontent');
   });
+
   it('GH-1089: should support failover content in multiple levels of inline partials', function() {
-    var string = '{{#> layout}}{{/layout}}';
-    var partials = {
-      doctype: 'doctype{{> content}}',
-      layout:
-        '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
-    };
-    shouldCompileToWithPartials(
-      string,
-      [{}, {}, partials],
-      true,
-      'doctypelayoutsubcontent'
-    );
+    expectTemplate('{{#> layout}}{{/layout}}')
+      .withPartials({
+        doctype: 'doctype{{> content}}',
+        layout:
+          '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
+      })
+      .toCompileTo('doctypelayoutsubcontent');
   });
+
   it('GH-1099: should support greater than 3 nested levels of inline partials', function() {
-    var string = '{{#> layout}}Outer{{/layout}}';
-    var partials = {
-      layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}',
-      inner: ''
-    };
-    shouldCompileToWithPartials(string, [{}, {}, partials], true, 'Outer');
+    expectTemplate('{{#> layout}}Outer{{/layout}}')
+      .withPartials({
+        layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}',
+        inner: ''
+      })
+      .toCompileTo('Outer');
   });
 
   it('GH-1135 : Context handling within each iteration', function() {
-    var obj = { array: [1], name: 'John' };
-    var helpers = {
-      myif: function(conditional, options) {
-        if (conditional) {
-          return options.fn(this);
-        } else {
-          return options.inverse(this);
-        }
-      }
-    };
-
-    shouldCompileTo(
+    expectTemplate(
       '{{#each array}}\n' +
         ' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' +
         ' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' +
-        '{{/each}}',
-      [obj, helpers],
-      ' 1. IF: John--\n' + ' 2. MYIF: John==\n'
-    );
+        '{{/each}}'
+    )
+      .withInput({ array: [1], name: 'John' })
+      .withHelpers({
+        myif: function(conditional, options) {
+          if (conditional) {
+            return options.fn(this);
+          } else {
+            return options.inverse(this);
+          }
+        }
+      })
+      .toCompileTo(' 1. IF: John--\n' + ' 2. MYIF: John==\n');
   });
 
   it('GH-1186: Support block params for existing programs', function() {
-    var string =
+    expectTemplate(
       '{{#*inline "test"}}{{> @partial-block }}{{/inline}}' +
-      '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' +
-      '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}';
-
-    shouldCompileTo(string, { listOne: ['a'], listTwo: ['b'] }, 'ab', '');
+        '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' +
+        '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}'
+    )
+      .withInput({
+        listOne: ['a'],
+        listTwo: ['b']
+      })
+      .withMessage('')
+      .toCompileTo('ab');
   });
 
   it('GH-1319: "unless" breaks when "each" value equals "null"', function() {
-    var string =
-      '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}';
-    shouldCompileTo(
-      string,
-      { value: 'parent', list: [null, 'a'] },
-      'parent=parent parent=parent ',
-      ''
-    );
+    expectTemplate(
+      '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}'
+    )
+      .withInput({
+        value: 'parent',
+        list: [null, 'a']
+      })
+      .withMessage('')
+      .toCompileTo('parent=parent parent=parent ');
   });
 
   it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() {
-    var string = 'template {{>partial}} template';
-    var partials = {
-      partialWithBlock:
-        '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
-      partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}'
-    };
-    shouldCompileToWithPartials(
-      string,
-      [{}, {}, partials],
-      true,
-      'template  block  partial  block  template'
-    );
+    expectTemplate('template {{>partial}} template')
+      .withPartials({
+        partialWithBlock:
+          '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
+        partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}'
+      })
+      .toCompileTo('template  block  partial  block  template');
   });
 
   describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function() {
@@ -482,14 +455,14 @@ describe('Regressions', function() {
   });
 
   it('should allow hash with protected array names', function() {
-    var obj = { array: [1], name: 'John' };
-    var helpers = {
-      helpa: function(options) {
-        return options.hash.length;
-      }
-    };
-
-    shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo');
+    expectTemplate('{{helpa length="foo"}}')
+      .withInput({ array: [1], name: 'John' })
+      .withHelpers({
+        helpa: function(options) {
+          return options.hash.length;
+        }
+      })
+      .toCompileTo('foo');
   });
 
   describe('GH-1598: Performance degradation for partials since v4.3.0', function() {
diff --git a/spec/security.js b/spec/security.js
index 1b345f0c..25aabeb7 100644
--- a/spec/security.js
+++ b/spec/security.js
@@ -20,36 +20,19 @@ describe('security issues', function() {
     });
 
     it('should allow the "constructor" property to be accessed if it is an "ownProperty"', function() {
-      shouldCompileTo(
-        '{{constructor.name}}',
-        {
-          constructor: {
-            name: 'here we go'
-          }
-        },
-        'here we go'
-      );
-      shouldCompileTo(
-        '{{lookup (lookup this "constructor") "name"}}',
-        {
-          constructor: {
-            name: 'here we go'
-          }
-        },
-        'here we go'
-      );
+      expectTemplate('{{constructor.name}}')
+        .withInput({ constructor: { name: 'here we go' } })
+        .toCompileTo('here we go');
+
+      expectTemplate('{{lookup (lookup this "constructor") "name"}}')
+        .withInput({ constructor: { name: 'here we go' } })
+        .toCompileTo('here we go');
     });
 
     it('should allow the "constructor" property to be accessed if it is an "own property"', function() {
-      shouldCompileTo(
-        '{{lookup (lookup this "constructor") "name"}}',
-        {
-          constructor: {
-            name: 'here we go'
-          }
-        },
-        'here we go'
-      );
+      expectTemplate('{{lookup (lookup this "constructor") "name"}}')
+        .withInput({ constructor: { name: 'here we go' } })
+        .toCompileTo('here we go');
     });
   });
 
@@ -60,19 +43,13 @@ describe('security issues', function() {
 
     describe('without the option "allowExplicitCallOfHelperMissing"', function() {
       it('should throw an exception when calling  "{{helperMissing}}" ', function() {
-        shouldThrow(function() {
-          var template = Handlebars.compile('{{helperMissing}}');
-          template({});
-        }, Error);
+        expectTemplate('{{helperMissing}}').toThrow(Error);
       });
+
       it('should throw an exception when calling  "{{#helperMissing}}{{/helperMissing}}" ', function() {
-        shouldThrow(function() {
-          var template = Handlebars.compile(
-            '{{#helperMissing}}{{/helperMissing}}'
-          );
-          template({});
-        }, Error);
+        expectTemplate('{{#helperMissing}}{{/helperMissing}}').toThrow(Error);
       });
+
       it('should throw an exception when calling  "{{blockHelperMissing "abc" .}}" ', function() {
         var functionCalls = [];
         expect(function() {
@@ -85,17 +62,15 @@ describe('security issues', function() {
         }).to.throw(Error);
         expect(functionCalls.length).to.equal(0);
       });
+
       it('should throw an exception when calling  "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
-        shouldThrow(function() {
-          var template = Handlebars.compile(
-            '{{#blockHelperMissing .}}{{/blockHelperMissing}}'
-          );
-          template({
+        expectTemplate('{{#blockHelperMissing .}}{{/blockHelperMissing}}')
+          .withInput({
             fn: function() {
               return 'functionInData';
             }
-          });
-        }, Error);
+          })
+          .toThrow(Error);
       });
     });
 
@@ -104,12 +79,14 @@ describe('security issues', function() {
         var template = Handlebars.compile('{{helperMissing}}');
         template({}, { allowCallsToHelperMissing: true });
       });
+
       it('should not throw an exception when calling  "{{#helperMissing}}{{/helperMissing}}" ', function() {
         var template = Handlebars.compile(
           '{{#helperMissing}}{{/helperMissing}}'
         );
         template({}, { allowCallsToHelperMissing: true });
       });
+
       it('should not throw an exception when calling  "{{blockHelperMissing "abc" .}}" ', function() {
         var functionCalls = [];
         var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
@@ -123,6 +100,7 @@ describe('security issues', function() {
         );
         equals(functionCalls.length, 1);
       });
+
       it('should not throw an exception when calling  "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
         var template = Handlebars.compile(
           '{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}'
diff --git a/spec/spec.js b/spec/spec.js
index ff4d7bc9..8b4ea99f 100644
--- a/spec/spec.js
+++ b/spec/spec.js
@@ -40,22 +40,12 @@ describe('spec', function() {
         /* eslint-enable no-eval */
       }
       it(name + ' - ' + test.name, function() {
-        if (test.partials) {
-          shouldCompileToWithPartials(
-            test.template,
-            [data, {}, test.partials, true],
-            true,
-            test.expected,
-            test.desc + ' "' + test.template + '"'
-          );
-        } else {
-          shouldCompileTo(
-            test.template,
-            [data, {}, {}, true],
-            test.expected,
-            test.desc + ' "' + test.template + '"'
-          );
-        }
+        expectTemplate(test.template)
+          .withInput(data)
+          .withPartials(test.partials || {})
+          .withCompileOptions({ compat: true })
+          .withMessage(test.desc + ' "' + test.template + '"')
+          .toCompileTo(test.expected);
       });
     });
   });
diff --git a/spec/strict.js b/spec/strict.js
index 680bbc0d..8e48fab5 100644
--- a/spec/strict.js
+++ b/spec/strict.js
@@ -3,161 +3,107 @@ var Exception = Handlebars.Exception;
 describe('strict', function() {
   describe('strict mode', function() {
     it('should error on missing property lookup', function() {
-      shouldThrow(
-        function() {
-          var template = CompilerContext.compile('{{hello}}', { strict: true });
-
-          template({});
-        },
-        Exception,
-        /"hello" not defined in/
-      );
+      expectTemplate('{{hello}}')
+        .withCompileOptions({ strict: true })
+        .toThrow(Exception, /"hello" not defined in/);
     });
+
     it('should error on missing child', function() {
-      var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
-      equals(template({ hello: { bar: 'foo' } }), 'foo');
+      expectTemplate('{{hello.bar}}')
+        .withCompileOptions({ strict: true })
+        .withInput({ hello: { bar: 'foo' } })
+        .toCompileTo('foo');
 
-      shouldThrow(
-        function() {
-          template({ hello: {} });
-        },
-        Exception,
-        /"bar" not defined in/
-      );
+      expectTemplate('{{hello.bar}}')
+        .withCompileOptions({ strict: true })
+        .withInput({ hello: {} })
+        .toThrow(Exception, /"bar" not defined in/);
     });
-    it('should handle explicit undefined', function() {
-      var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
 
-      equals(template({ hello: { bar: undefined } }), '');
+    it('should handle explicit undefined', function() {
+      expectTemplate('{{hello.bar}}')
+        .withCompileOptions({ strict: true })
+        .withInput({ hello: { bar: undefined } })
+        .toCompileTo('');
     });
+
     it('should error on missing property lookup in known helpers mode', function() {
-      shouldThrow(
-        function() {
-          var template = CompilerContext.compile('{{hello}}', {
-            strict: true,
-            knownHelpersOnly: true
-          });
-
-          template({});
-        },
-        Exception,
-        /"hello" not defined in/
-      );
+      expectTemplate('{{hello}}')
+        .withCompileOptions({
+          strict: true,
+          knownHelpersOnly: true
+        })
+        .toThrow(Exception, /"hello" not defined in/);
     });
-    it('should error on missing context', function() {
-      shouldThrow(function() {
-        var template = CompilerContext.compile('{{hello}}', { strict: true });
 
-        template();
-      }, Error);
+    it('should error on missing context', function() {
+      expectTemplate('{{hello}}')
+        .withCompileOptions({ strict: true })
+        .toThrow(Error);
     });
 
     it('should error on missing data lookup', function() {
-      var template = CompilerContext.compile('{{@hello}}', { strict: true });
-      equals(template(undefined, { data: { hello: 'foo' } }), 'foo');
+      var xt = expectTemplate('{{@hello}}').withCompileOptions({
+        strict: true
+      });
 
-      shouldThrow(function() {
-        template();
-      }, Error);
+      xt.toThrow(Error);
+
+      xt.withRuntimeOptions({ data: { hello: 'foo' } }).toCompileTo('foo');
     });
 
     it('should not run helperMissing for helper calls', function() {
-      shouldThrow(
-        function() {
-          var template = CompilerContext.compile('{{hello foo}}', {
-            strict: true
-          });
-
-          template({ foo: true });
-        },
-        Exception,
-        /"hello" not defined in/
-      );
-
-      shouldThrow(
-        function() {
-          var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {
-            strict: true
-          });
-
-          template({ foo: true });
-        },
-        Exception,
-        /"hello" not defined in/
-      );
+      expectTemplate('{{hello foo}}')
+        .withCompileOptions({ strict: true })
+        .withInput({ foo: true })
+        .toThrow(Exception, /"hello" not defined in/);
+
+      expectTemplate('{{#hello foo}}{{/hello}}')
+        .withCompileOptions({ strict: true })
+        .withInput({ foo: true })
+        .toThrow(Exception, /"hello" not defined in/);
     });
+
     it('should throw on ambiguous blocks', function() {
-      shouldThrow(
-        function() {
-          var template = CompilerContext.compile('{{#hello}}{{/hello}}', {
-            strict: true
-          });
-
-          template({});
-        },
-        Exception,
-        /"hello" not defined in/
-      );
-
-      shouldThrow(
-        function() {
-          var template = CompilerContext.compile('{{^hello}}{{/hello}}', {
-            strict: true
-          });
-
-          template({});
-        },
-        Exception,
-        /"hello" not defined in/
-      );
-
-      shouldThrow(
-        function() {
-          var template = CompilerContext.compile(
-            '{{#hello.bar}}{{/hello.bar}}',
-            { strict: true }
-          );
-
-          template({ hello: {} });
-        },
-        Exception,
-        /"bar" not defined in/
-      );
+      expectTemplate('{{#hello}}{{/hello}}')
+        .withCompileOptions({ strict: true })
+        .toThrow(Exception, /"hello" not defined in/);
+
+      expectTemplate('{{^hello}}{{/hello}}')
+        .withCompileOptions({ strict: true })
+        .toThrow(Exception, /"hello" not defined in/);
+
+      expectTemplate('{{#hello.bar}}{{/hello.bar}}')
+        .withCompileOptions({ strict: true })
+        .withInput({ hello: {} })
+        .toThrow(Exception, /"bar" not defined in/);
     });
 
     it('should allow undefined parameters when passed to helpers', function() {
-      var template = CompilerContext.compile(
-        '{{#unless foo}}success{{/unless}}',
-        { strict: true }
-      );
-      equals(template({}), 'success');
+      expectTemplate('{{#unless foo}}success{{/unless}}')
+        .withCompileOptions({ strict: true })
+        .toCompileTo('success');
     });
 
     it('should allow undefined hash when passed to helpers', function() {
-      var template = CompilerContext.compile('{{helper value=@foo}}', {
-        strict: true
-      });
-      var helpers = {
-        helper: function(options) {
-          equals('value' in options.hash, true);
-          equals(options.hash.value, undefined);
-          return 'success';
-        }
-      };
-      equals(template({}, { helpers: helpers }), 'success');
+      expectTemplate('{{helper value=@foo}}')
+        .withCompileOptions({
+          strict: true
+        })
+        .withHelpers({
+          helper: function(options) {
+            equals('value' in options.hash, true);
+            equals(options.hash.value, undefined);
+            return 'success';
+          }
+        })
+        .toCompileTo('success');
     });
 
     it('should show error location on missing property lookup', function() {
-      shouldThrow(
-        function() {
-          var template = CompilerContext.compile('\n\n\n   {{hello}}', {
-            strict: true
-          });
-          template({});
-        },
-        Exception,
-        '"hello" not defined in [object Object] - 4:5'
-      );
+      expectTemplate('\n\n\n   {{hello}}')
+        .withCompileOptions({ strict: true })
+        .toThrow(Exception, '"hello" not defined in [object Object] - 4:5');
     });
 
     it('should error contains correct location properties on missing property lookup', function() {
@@ -177,54 +123,42 @@ describe('strict', function() {
 
   describe('assume objects', function() {
     it('should ignore missing property', function() {
-      var template = CompilerContext.compile('{{hello}}', {
-        assumeObjects: true
-      });
-
-      equal(template({}), '');
+      expectTemplate('{{hello}}')
+        .withCompileOptions({ assumeObjects: true })
+        .toCompileTo('');
     });
-    it('should ignore missing child', function() {
-      var template = CompilerContext.compile('{{hello.bar}}', {
-        assumeObjects: true
-      });
 
-      equal(template({ hello: {} }), '');
+    it('should ignore missing child', function() {
+      expectTemplate('{{hello.bar}}')
+        .withCompileOptions({ assumeObjects: true })
+        .withInput({ hello: {} })
+        .toCompileTo('');
     });
-    it('should error on missing object', function() {
-      shouldThrow(function() {
-        var template = CompilerContext.compile('{{hello.bar}}', {
-          assumeObjects: true
-        });
 
-        template({});
-      }, Error);
+    it('should error on missing object', function() {
+      expectTemplate('{{hello.bar}}')
+        .withCompileOptions({ assumeObjects: true })
+        .toThrow(Error);
     });
-    it('should error on missing context', function() {
-      shouldThrow(function() {
-        var template = CompilerContext.compile('{{hello}}', {
-          assumeObjects: true
-        });
 
-        template();
-      }, Error);
+    it('should error on missing context', function() {
+      expectTemplate('{{hello}}')
+        .withCompileOptions({ assumeObjects: true })
+        .withInput(undefined)
+        .toThrow(Error);
     });
 
     it('should error on missing data lookup', function() {
-      shouldThrow(function() {
-        var template = CompilerContext.compile('{{@hello.bar}}', {
-          assumeObjects: true
-        });
-
-        template();
-      }, Error);
+      expectTemplate('{{@hello.bar}}')
+        .withCompileOptions({ assumeObjects: true })
+        .withInput(undefined)
+        .toThrow(Error);
     });
 
     it('should execute blockHelperMissing', function() {
-      var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', {
-        assumeObjects: true
-      });
-
-      equals(template({}), 'foo');
+      expectTemplate('{{^hello}}foo{{/hello}}')
+        .withCompileOptions({ assumeObjects: true })
+        .toCompileTo('foo');
     });
   });
 });
diff --git a/spec/string-params.js b/spec/string-params.js
index 9f83611a..c4b9a27b 100644
--- a/spec/string-params.js
+++ b/spec/string-params.js
@@ -1,151 +1,117 @@
 describe('string params mode', function() {
   it('arguments to helpers can be retrieved from options hash in string form', function() {
-    var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {
-      stringParams: true
-    });
-
-    var helpers = {
-      wycats: function(passiveVoice, noun) {
-        return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun;
-      }
-    };
-
-    var result = template({}, { helpers: helpers });
-
-    equals(
-      result,
-      'HELP ME MY BOSS is.a slave.driver',
-      'String parameters output'
-    );
+    expectTemplate('{{wycats is.a slave.driver}}')
+      .withCompileOptions({
+        stringParams: true
+      })
+      .withHelpers({
+        wycats: function(passiveVoice, noun) {
+          return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun;
+        }
+      })
+      .withMessage('String parameters output')
+      .toCompileTo('HELP ME MY BOSS is.a slave.driver');
   });
 
   it('when using block form, arguments to helpers can be retrieved from options hash in string form', function() {
-    var template = CompilerContext.compile(
-      '{{#wycats is.a slave.driver}}help :({{/wycats}}',
-      { stringParams: true }
-    );
-
-    var helpers = {
-      wycats: function(passiveVoice, noun, options) {
-        return (
-          'HELP ME MY BOSS ' +
-          passiveVoice +
-          ' ' +
-          noun +
-          ': ' +
-          options.fn(this)
-        );
-      }
-    };
-
-    var result = template({}, { helpers: helpers });
-
-    equals(
-      result,
-      'HELP ME MY BOSS is.a slave.driver: help :(',
-      'String parameters output'
-    );
+    expectTemplate('{{#wycats is.a slave.driver}}help :({{/wycats}}')
+      .withCompileOptions({
+        stringParams: true
+      })
+      .withHelpers({
+        wycats: function(passiveVoice, noun, options) {
+          return (
+            'HELP ME MY BOSS ' +
+            passiveVoice +
+            ' ' +
+            noun +
+            ': ' +
+            options.fn(this)
+          );
+        }
+      })
+      .withMessage('String parameters output')
+      .toCompileTo('HELP ME MY BOSS is.a slave.driver: help :(');
   });
 
   it('when inside a block in String mode, .. passes the appropriate context in the options hash', function() {
-    var template = CompilerContext.compile(
-      '{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}',
-      { stringParams: true }
-    );
-
-    var helpers = {
-      tomdale: function(desire, noun, options) {
-        return (
-          'STOP ME FROM READING HACKER NEWS I ' +
-          options.contexts[0][desire] +
-          ' ' +
-          noun
-        );
-      },
-
-      with: function(context, options) {
-        return options.fn(options.contexts[0][context]);
-      }
-    };
-
-    var result = template(
-      {
+    expectTemplate('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}')
+      .withCompileOptions({
+        stringParams: true
+      })
+      .withHelpers({
+        tomdale: function(desire, noun, options) {
+          return (
+            'STOP ME FROM READING HACKER NEWS I ' +
+            options.contexts[0][desire] +
+            ' ' +
+            noun
+          );
+        },
+        with: function(context, options) {
+          return options.fn(options.contexts[0][context]);
+        }
+      })
+      .withInput({
         dale: {},
 
         need: 'need-a'
-      },
-      { helpers: helpers }
-    );
-
-    equals(
-      result,
-      'STOP ME FROM READING HACKER NEWS I need-a dad.joke',
-      'Proper context variable output'
-    );
+      })
+      .withMessage('Proper context variable output')
+      .toCompileTo('STOP ME FROM READING HACKER NEWS I need-a dad.joke');
   });
 
   it('information about the types is passed along', function() {
-    var template = CompilerContext.compile(
-      "{{tomdale 'need' dad.joke true false}}",
-      { stringParams: true }
-    );
-
-    var helpers = {
-      tomdale: function(desire, noun, trueBool, falseBool, options) {
-        equal(options.types[0], 'StringLiteral', 'the string type is passed');
-        equal(
-          options.types[1],
-          'PathExpression',
-          'the expression type is passed'
-        );
-        equal(
-          options.types[2],
-          'BooleanLiteral',
-          'the expression type is passed'
-        );
-        equal(desire, 'need', 'the string form is passed for strings');
-        equal(noun, 'dad.joke', 'the string form is passed for expressions');
-        equal(trueBool, true, 'raw booleans are passed through');
-        equal(falseBool, false, 'raw booleans are passed through');
-        return 'Helper called';
-      }
-    };
-
-    var result = template({}, { helpers: helpers });
-    equal(result, 'Helper called');
+    expectTemplate("{{tomdale 'need' dad.joke true false}}")
+      .withCompileOptions({
+        stringParams: true
+      })
+      .withHelpers({
+        tomdale: function(desire, noun, trueBool, falseBool, options) {
+          equal(options.types[0], 'StringLiteral', 'the string type is passed');
+          equal(
+            options.types[1],
+            'PathExpression',
+            'the expression type is passed'
+          );
+          equal(
+            options.types[2],
+            'BooleanLiteral',
+            'the expression type is passed'
+          );
+          equal(desire, 'need', 'the string form is passed for strings');
+          equal(noun, 'dad.joke', 'the string form is passed for expressions');
+          equal(trueBool, true, 'raw booleans are passed through');
+          equal(falseBool, false, 'raw booleans are passed through');
+          return 'Helper called';
+        }
+      })
+      .toCompileTo('Helper called');
   });
 
   it('hash parameters get type information', function() {
-    var template = CompilerContext.compile(
-      "{{tomdale he.says desire='need' noun=dad.joke bool=true}}",
-      { stringParams: true }
-    );
-
-    var helpers = {
-      tomdale: function(exclamation, options) {
-        equal(exclamation, 'he.says');
-        equal(options.types[0], 'PathExpression');
-
-        equal(options.hashTypes.desire, 'StringLiteral');
-        equal(options.hashTypes.noun, 'PathExpression');
-        equal(options.hashTypes.bool, 'BooleanLiteral');
-        equal(options.hash.desire, 'need');
-        equal(options.hash.noun, 'dad.joke');
-        equal(options.hash.bool, true);
-        return 'Helper called';
-      }
-    };
-
-    var result = template({}, { helpers: helpers });
-    equal(result, 'Helper called');
+    expectTemplate("{{tomdale he.says desire='need' noun=dad.joke bool=true}}")
+      .withCompileOptions({
+        stringParams: true
+      })
+      .withHelpers({
+        tomdale: function(exclamation, options) {
+          equal(exclamation, 'he.says');
+          equal(options.types[0], 'PathExpression');
+
+          equal(options.hashTypes.desire, 'StringLiteral');
+          equal(options.hashTypes.noun, 'PathExpression');
+          equal(options.hashTypes.bool, 'BooleanLiteral');
+          equal(options.hash.desire, 'need');
+          equal(options.hash.noun, 'dad.joke');
+          equal(options.hash.bool, true);
+          return 'Helper called';
+        }
+      })
+      .toCompileTo('Helper called');
   });
 
   it('hash parameters get context information', function() {
-    var template = CompilerContext.compile(
-      "{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}",
-      { stringParams: true }
-    );
-
     var context = { dale: {} };
 
     var helpers = {
@@ -165,82 +131,77 @@ describe('string params mode', function() {
       }
     };
 
-    var result = template(context, { helpers: helpers });
-    equal(result, 'Helper called');
+    expectTemplate(
+      "{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}"
+    )
+      .withCompileOptions({ stringParams: true })
+      .withHelpers(helpers)
+      .withInput(context)
+      .toCompileTo('Helper called');
   });
 
   it('when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper', function() {
-    var template = CompilerContext.compile(
-      '{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}',
-      { stringParams: true }
-    );
-
-    var helpers = {
-      tomdale: function(desire, noun, options) {
-        return (
-          'STOP ME FROM READING HACKER NEWS I ' +
-          options.contexts[0][desire] +
-          ' ' +
-          noun +
-          ' ' +
-          options.fn(this)
-        );
-      },
-
-      with: function(context, options) {
-        return options.fn(options.contexts[0][context]);
-      }
-    };
-
-    var result = template(
-      {
+    expectTemplate(
+      '{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}'
+    )
+      .withCompileOptions({
+        stringParams: true
+      })
+      .withHelpers({
+        tomdale: function(desire, noun, options) {
+          return (
+            'STOP ME FROM READING HACKER NEWS I ' +
+            options.contexts[0][desire] +
+            ' ' +
+            noun +
+            ' ' +
+            options.fn(this)
+          );
+        },
+
+        with: function(context, options) {
+          return options.fn(options.contexts[0][context]);
+        }
+      })
+      .withInput({
         dale: {},
 
         need: 'need-a'
-      },
-      { helpers: helpers }
-    );
-
-    equals(
-      result,
-      'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot',
-      'Proper context variable output'
-    );
+      })
+      .withMessage('Proper context variable output')
+      .toCompileTo('STOP ME FROM READING HACKER NEWS I need-a dad.joke wot');
   });
 
   it('with nested block ambiguous', function() {
-    var template = CompilerContext.compile(
-      '{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}',
-      { stringParams: true }
-    );
-
-    var helpers = {
-      with: function() {
-        return 'WITH';
-      },
-      view: function() {
-        return 'VIEW';
-      }
-    };
-
-    var result = template({}, { helpers: helpers });
-    equals(result, 'WITH');
+    expectTemplate(
+      '{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}'
+    )
+      .withCompileOptions({
+        stringParams: true
+      })
+      .withHelpers({
+        with: function() {
+          return 'WITH';
+        },
+        view: function() {
+          return 'VIEW';
+        }
+      })
+      .toCompileTo('WITH');
   });
 
   it('should handle DATA', function() {
-    var template = CompilerContext.compile('{{foo @bar}}', {
-      stringParams: true
-    });
-
-    var helpers = {
-      foo: function(bar, options) {
-        equal(bar, '@bar');
-        equal(options.types[0], 'PathExpression');
-        return 'Foo!';
-      }
-    };
-
-    var result = template({}, { helpers: helpers });
-    equal(result, 'Foo!');
+    expectTemplate('{{foo @bar}}')
+      .withCompileOptions({
+        stringParams: true
+      })
+      .withHelpers({
+        foo: function(bar, options) {
+          equal(bar, '@bar');
+          equal(options.types[0], 'PathExpression');
+          return 'Foo!';
+        }
+      })
+      .toCompileTo('Foo!');
   });
 });
diff --git a/spec/subexpressions.js b/spec/subexpressions.js
index 22851051..21b93aab 100644
--- a/spec/subexpressions.js
+++ b/spec/subexpressions.js
@@ -1,61 +1,57 @@
 describe('subexpressions', function() {
   it('arg-less helper', function() {
-    var string = '{{foo (bar)}}!';
-    var context = {};
-    var helpers = {
-      foo: function(val) {
-        return val + val;
-      },
-      bar: function() {
-        return 'LOL';
-      }
-    };
-    shouldCompileTo(string, [context, helpers], 'LOLLOL!');
+    expectTemplate('{{foo (bar)}}!')
+      .withHelpers({
+        foo: function(val) {
+          return val + val;
+        },
+        bar: function() {
+          return 'LOL';
+        }
+      })
+      .toCompileTo('LOLLOL!');
   });
 
   it('helper w args', function() {
-    var string = '{{blog (equal a b)}}';
-
-    var context = { bar: 'LOL' };
-    var helpers = {
-      blog: function(val) {
-        return 'val is ' + val;
-      },
-      equal: function(x, y) {
-        return x === y;
-      }
-    };
-    shouldCompileTo(string, [context, helpers], 'val is true');
+    expectTemplate('{{blog (equal a b)}}')
+      .withInput({ bar: 'LOL' })
+      .withHelpers({
+        blog: function(val) {
+          return 'val is ' + val;
+        },
+        equal: function(x, y) {
+          return x === y;
+        }
+      })
+      .toCompileTo('val is true');
   });
 
   it('mixed paths and helpers', function() {
-    var string = '{{blog baz.bat (equal a b) baz.bar}}';
-
-    var context = { bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } };
-    var helpers = {
-      blog: function(val, that, theOther) {
-        return 'val is ' + val + ', ' + that + ' and ' + theOther;
-      },
-      equal: function(x, y) {
-        return x === y;
-      }
-    };
-    shouldCompileTo(string, [context, helpers], 'val is foo!, true and bar!');
+    expectTemplate('{{blog baz.bat (equal a b) baz.bar}}')
+      .withInput({ bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } })
+      .withHelpers({
+        blog: function(val, that, theOther) {
+          return 'val is ' + val + ', ' + that + ' and ' + theOther;
+        },
+        equal: function(x, y) {
+          return x === y;
+        }
+      })
+      .toCompileTo('val is foo!, true and bar!');
   });
 
   it('supports much nesting', function() {
-    var string = '{{blog (equal (equal true true) true)}}';
-
-    var context = { bar: 'LOL' };
-    var helpers = {
-      blog: function(val) {
-        return 'val is ' + val;
-      },
-      equal: function(x, y) {
-        return x === y;
-      }
-    };
-    shouldCompileTo(string, [context, helpers], 'val is true');
+    expectTemplate('{{blog (equal (equal true true) true)}}')
+      .withInput({ bar: 'LOL' })
+      .withHelpers({
+        blog: function(val) {
+          return 'val is ' + val;
+        },
+        equal: function(x, y) {
+          return x === y;
+        }
+      })
+      .toCompileTo('val is true');
   });
 
   it('GH-800 : Complex subexpressions', function() {
@@ -69,20 +65,33 @@ describe('subexpressions', function() {
       }
     };
 
-    shouldCompileTo(
-      "{{dash 'abc' (concat a b)}}",
-      [context, helpers],
-      'abc-ab'
-    );
-    shouldCompileTo('{{dash d (concat a b)}}', [context, helpers], 'd-ab');
-    shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab');
-    shouldCompileTo('{{dash (concat a b) c.c}}', [context, helpers], 'ab-c');
-    shouldCompileTo('{{dash (concat a e.e) c.c}}', [context, helpers], 'ae-c');
+    expectTemplate("{{dash 'abc' (concat a b)}}")
+      .withInput(context)
+      .withHelpers(helpers)
+      .toCompileTo('abc-ab');
+
+    expectTemplate('{{dash d (concat a b)}}')
+      .withInput(context)
+      .withHelpers(helpers)
+      .toCompileTo('d-ab');
+
+    expectTemplate('{{dash c.c (concat a b)}}')
+      .withInput(context)
+      .withHelpers(helpers)
+      .toCompileTo('c-ab');
+
+    expectTemplate('{{dash (concat a b) c.c}}')
+      .withInput(context)
+      .withHelpers(helpers)
+      .toCompileTo('ab-c');
+
+    expectTemplate('{{dash (concat a e.e) c.c}}')
+      .withInput(context)
+      .withHelpers(helpers)
+      .toCompileTo('ae-c');
   });
 
   it('provides each nested helper invocation its own options hash', function() {
-    var string = '{{equal (equal true true) true}}';
-
     var lastOptions = null;
     var helpers = {
       equal: function(x, y, options) {
@@ -93,200 +102,177 @@ describe('subexpressions', function() {
         return x === y;
       }
     };
-    shouldCompileTo(string, [{}, helpers], 'true');
+    expectTemplate('{{equal (equal true true) true}}')
+      .withHelpers(helpers)
+      .toCompileTo('true');
   });
 
   it('with hashes', function() {
-    var string = "{{blog (equal (equal true true) true fun='yes')}}";
-
-    var context = { bar: 'LOL' };
-    var helpers = {
-      blog: function(val) {
-        return 'val is ' + val;
-      },
-      equal: function(x, y) {
-        return x === y;
-      }
-    };
-    shouldCompileTo(string, [context, helpers], 'val is true');
+    expectTemplate("{{blog (equal (equal true true) true fun='yes')}}")
+      .withInput({ bar: 'LOL' })
+      .withHelpers({
+        blog: function(val) {
+          return 'val is ' + val;
+        },
+        equal: function(x, y) {
+          return x === y;
+        }
+      })
+      .toCompileTo('val is true');
   });
 
   it('as hashes', function() {
-    var string = "{{blog fun=(equal (blog fun=1) 'val is 1')}}";
-
-    var helpers = {
-      blog: function(options) {
-        return 'val is ' + options.hash.fun;
-      },
-      equal: function(x, y) {
-        return x === y;
-      }
-    };
-    shouldCompileTo(string, [{}, helpers], 'val is true');
+    expectTemplate("{{blog fun=(equal (blog fun=1) 'val is 1')}}")
+      .withHelpers({
+        blog: function(options) {
+          return 'val is ' + options.hash.fun;
+        },
+        equal: function(x, y) {
+          return x === y;
+        }
+      })
+      .toCompileTo('val is true');
   });
 
   it('multiple subexpressions in a hash', function() {
-    var string =
-      '{{input aria-label=(t "Name") placeholder=(t "Example User")}}';
-
-    var helpers = {
-      input: function(options) {
-        var hash = options.hash;
-        var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
-        var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
-        return new Handlebars.SafeString(
-          '<input aria-label="' +
-            ariaLabel +
-            '" placeholder="' +
-            placeholder +
-            '" />'
-        );
-      },
-      t: function(defaultString) {
-        return new Handlebars.SafeString(defaultString);
-      }
-    };
-    shouldCompileTo(
-      string,
-      [{}, helpers],
-      '<input aria-label="Name" placeholder="Example User" />'
-    );
+    expectTemplate(
+      '{{input aria-label=(t "Name") placeholder=(t "Example User")}}'
+    )
+      .withHelpers({
+        input: function(options) {
+          var hash = options.hash;
+          var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
+          var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
+          return new Handlebars.SafeString(
+            '<input aria-label="' +
+              ariaLabel +
+              '" placeholder="' +
+              placeholder +
+              '" />'
+          );
+        },
+        t: function(defaultString) {
+          return new Handlebars.SafeString(defaultString);
+        }
+      })
+      .toCompileTo('<input aria-label="Name" placeholder="Example User" />');
   });
 
   it('multiple subexpressions in a hash with context', function() {
-    var string =
-      '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}';
-
-    var context = {
-      item: {
-        field: 'Name',
-        placeholder: 'Example User'
-      }
-    };
-
-    var helpers = {
-      input: function(options) {
-        var hash = options.hash;
-        var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
-        var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
-        return new Handlebars.SafeString(
-          '<input aria-label="' +
-            ariaLabel +
-            '" placeholder="' +
-            placeholder +
-            '" />'
-        );
-      },
-      t: function(defaultString) {
-        return new Handlebars.SafeString(defaultString);
-      }
-    };
-    shouldCompileTo(
-      string,
-      [context, helpers],
-      '<input aria-label="Name" placeholder="Example User" />'
-    );
+    expectTemplate(
+      '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}'
+    )
+      .withInput({
+        item: {
+          field: 'Name',
+          placeholder: 'Example User'
+        }
+      })
+      .withHelpers({
+        input: function(options) {
+          var hash = options.hash;
+          var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
+          var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
+          return new Handlebars.SafeString(
+            '<input aria-label="' +
+              ariaLabel +
+              '" placeholder="' +
+              placeholder +
+              '" />'
+          );
+        },
+        t: function(defaultString) {
+          return new Handlebars.SafeString(defaultString);
+        }
+      })
+      .toCompileTo('<input aria-label="Name" placeholder="Example User" />');
   });
 
   it('in string params mode,', function() {
-    var template = CompilerContext.compile(
-      '{{snog (blorg foo x=y) yeah a=b}}',
-      { stringParams: true }
-    );
-
-    var helpers = {
-      snog: function(a, b, options) {
-        equals(a, 'foo');
-        equals(
-          options.types.length,
-          2,
-          'string params for outer helper processed correctly'
-        );
-        equals(
-          options.types[0],
-          'SubExpression',
-          'string params for outer helper processed correctly'
-        );
-        equals(
-          options.types[1],
-          'PathExpression',
-          'string params for outer helper processed correctly'
-        );
-        return a + b;
-      },
-
-      blorg: function(a, options) {
-        equals(
-          options.types.length,
-          1,
-          'string params for inner helper processed correctly'
-        );
-        equals(
-          options.types[0],
-          'PathExpression',
-          'string params for inner helper processed correctly'
-        );
-        return a;
-      }
-    };
+    expectTemplate('{{snog (blorg foo x=y) yeah a=b}}')
+      .withCompileOptions({ stringParams: true })
+      .withHelpers({
+        snog: function(a, b, options) {
+          equals(a, 'foo');
+          equals(
+            options.types.length,
+            2,
+            'string params for outer helper processed correctly'
+          );
+          equals(
+            options.types[0],
+            'SubExpression',
+            'string params for outer helper processed correctly'
+          );
+          equals(
+            options.types[1],
+            'PathExpression',
+            'string params for outer helper processed correctly'
+          );
+          return a + b;
+        },
 
-    var result = template(
-      {
+        blorg: function(a, options) {
+          equals(
+            options.types.length,
+            1,
+            'string params for inner helper processed correctly'
+          );
+          equals(
+            options.types[0],
+            'PathExpression',
+            'string params for inner helper processed correctly'
+          );
+          return a;
+        }
+      })
+      .withInput({
         foo: {},
         yeah: {}
-      },
-      { helpers: helpers }
-    );
-
-    equals(result, 'fooyeah');
+      })
+      .toCompileTo('fooyeah');
   });
 
   it('as hashes in string params mode', function() {
-    var template = CompilerContext.compile('{{blog fun=(bork)}}', {
-      stringParams: true
-    });
-
-    var helpers = {
-      blog: function(options) {
-        equals(options.hashTypes.fun, 'SubExpression');
-        return 'val is ' + options.hash.fun;
-      },
-      bork: function() {
-        return 'BORK';
-      }
-    };
-
-    var result = template({}, { helpers: helpers });
-    equals(result, 'val is BORK');
+    expectTemplate('{{blog fun=(bork)}}')
+      .withCompileOptions({ stringParams: true })
+      .withHelpers({
+        blog: function(options) {
+          equals(options.hashTypes.fun, 'SubExpression');
+          return 'val is ' + options.hash.fun;
+        },
+        bork: function() {
+          return 'BORK';
+        }
+      })
+      .toCompileTo('val is BORK');
   });
 
   it('subexpression functions on the context', function() {
-    var string = '{{foo (bar)}}!';
-    var context = {
-      bar: function() {
-        return 'LOL';
-      }
-    };
-    var helpers = {
-      foo: function(val) {
-        return val + val;
-      }
-    };
-    shouldCompileTo(string, [context, helpers], 'LOLLOL!');
+    expectTemplate('{{foo (bar)}}!')
+      .withInput({
+        bar: function() {
+          return 'LOL';
+        }
+      })
+      .withHelpers({
+        foo: function(val) {
+          return val + val;
+        }
+      })
+      .toCompileTo('LOLLOL!');
   });
 
   it("subexpressions can't just be property lookups", function() {
-    var string = '{{foo (bar)}}!';
-    var context = {
-      bar: 'LOL'
-    };
-    var helpers = {
-      foo: function(val) {
-        return val + val;
-      }
-    };
-    shouldThrow(function() {
-      shouldCompileTo(string, [context, helpers], 'LOLLOL!');
-    });
+    expectTemplate('{{foo (bar)}}!')
+      .withInput({
+        bar: 'LOL'
+      })
+      .withHelpers({
+        foo: function(val) {
+          return val + val;
+        }
+      })
+      .toThrow();
   });
 });
diff --git a/spec/track-ids.js b/spec/track-ids.js
index f7ad7abd..033e7b17 100644
--- a/spec/track-ids.js
+++ b/spec/track-ids.js
@@ -5,216 +5,184 @@ describe('track ids', function() {
   });
 
   it('should not include anything without the flag', function() {
-    var template = CompilerContext.compile('{{wycats is.a slave.driver}}');
-
-    var helpers = {
-      wycats: function(passiveVoice, noun, options) {
-        equal(options.ids, undefined);
-        equal(options.hashIds, undefined);
-
-        return 'success';
-      }
-    };
-
-    equals(template({}, { helpers: helpers }), 'success');
+    expectTemplate('{{wycats is.a slave.driver}}')
+      .withHelpers({
+        wycats: function(passiveVoice, noun, options) {
+          equal(options.ids, undefined);
+          equal(options.hashIds, undefined);
+
+          return 'success';
+        }
+      })
+      .toCompileTo('success');
   });
-  it('should include argument ids', function() {
-    var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {
-      trackIds: true
-    });
-
-    var helpers = {
-      wycats: function(passiveVoice, noun, options) {
-        equal(options.ids[0], 'is.a');
-        equal(options.ids[1], 'slave.driver');
-
-        return (
-          'HELP ME MY BOSS ' +
-          options.ids[0] +
-          ':' +
-          passiveVoice +
-          ' ' +
-          options.ids[1] +
-          ':' +
-          noun
-        );
-      }
-    };
 
-    equals(
-      template(context, { helpers: helpers }),
-      'HELP ME MY BOSS is.a:foo slave.driver:bar'
-    );
+  it('should include argument ids', function() {
+    expectTemplate('{{wycats is.a slave.driver}}')
+      .withCompileOptions({ trackIds: true })
+      .withHelpers({
+        wycats: function(passiveVoice, noun, options) {
+          equal(options.ids[0], 'is.a');
+          equal(options.ids[1], 'slave.driver');
+
+          return (
+            'HELP ME MY BOSS ' +
+            options.ids[0] +
+            ':' +
+            passiveVoice +
+            ' ' +
+            options.ids[1] +
+            ':' +
+            noun
+          );
+        }
+      })
+      .withInput(context)
+      .toCompileTo('HELP ME MY BOSS is.a:foo slave.driver:bar');
   });
-  it('should include hash ids', function() {
-    var template = CompilerContext.compile(
-      '{{wycats bat=is.a baz=slave.driver}}',
-      { trackIds: true }
-    );
-
-    var helpers = {
-      wycats: function(options) {
-        equal(options.hashIds.bat, 'is.a');
-        equal(options.hashIds.baz, 'slave.driver');
-
-        return (
-          'HELP ME MY BOSS ' +
-          options.hashIds.bat +
-          ':' +
-          options.hash.bat +
-          ' ' +
-          options.hashIds.baz +
-          ':' +
-          options.hash.baz
-        );
-      }
-    };
 
-    equals(
-      template(context, { helpers: helpers }),
-      'HELP ME MY BOSS is.a:foo slave.driver:bar'
-    );
+  it('should include hash ids', function() {
+    expectTemplate('{{wycats bat=is.a baz=slave.driver}}')
+      .withCompileOptions({ trackIds: true })
+      .withHelpers({
+        wycats: function(options) {
+          equal(options.hashIds.bat, 'is.a');
+          equal(options.hashIds.baz, 'slave.driver');
+
+          return (
+            'HELP ME MY BOSS ' +
+            options.hashIds.bat +
+            ':' +
+            options.hash.bat +
+            ' ' +
+            options.hashIds.baz +
+            ':' +
+            options.hash.baz
+          );
+        }
+      })
+      .withInput(context)
+      .toCompileTo('HELP ME MY BOSS is.a:foo slave.driver:bar');
   });
-  it('should note ../ and ./ references', function() {
-    var template = CompilerContext.compile(
-      '{{wycats ./is.a ../slave.driver this.is.a this}}',
-      { trackIds: true }
-    );
 
-    var helpers = {
-      wycats: function(passiveVoice, noun, thiz, thiz2, options) {
-        equal(options.ids[0], 'is.a');
-        equal(options.ids[1], '../slave.driver');
-        equal(options.ids[2], 'is.a');
-        equal(options.ids[3], '');
-
-        return (
-          'HELP ME MY BOSS ' +
-          options.ids[0] +
-          ':' +
-          passiveVoice +
-          ' ' +
-          options.ids[1] +
-          ':' +
-          noun
-        );
-      }
-    };
-
-    equals(
-      template(context, { helpers: helpers }),
-      'HELP ME MY BOSS is.a:foo ../slave.driver:undefined'
-    );
+  it('should note ../ and ./ references', function() {
+    expectTemplate('{{wycats ./is.a ../slave.driver this.is.a this}}')
+      .withCompileOptions({ trackIds: true })
+      .withHelpers({
+        wycats: function(passiveVoice, noun, thiz, thiz2, options) {
+          equal(options.ids[0], 'is.a');
+          equal(options.ids[1], '../slave.driver');
+          equal(options.ids[2], 'is.a');
+          equal(options.ids[3], '');
+
+          return (
+            'HELP ME MY BOSS ' +
+            options.ids[0] +
+            ':' +
+            passiveVoice +
+            ' ' +
+            options.ids[1] +
+            ':' +
+            noun
+          );
+        }
+      })
+      .withInput(context)
+      .toCompileTo('HELP ME MY BOSS is.a:foo ../slave.driver:undefined');
   });
-  it('should note @data references', function() {
-    var template = CompilerContext.compile('{{wycats @is.a @slave.driver}}', {
-      trackIds: true
-    });
 
-    var helpers = {
-      wycats: function(passiveVoice, noun, options) {
-        equal(options.ids[0], '@is.a');
-        equal(options.ids[1], '@slave.driver');
-
-        return (
-          'HELP ME MY BOSS ' +
-          options.ids[0] +
-          ':' +
-          passiveVoice +
-          ' ' +
-          options.ids[1] +
-          ':' +
-          noun
-        );
-      }
-    };
-
-    equals(
-      template({}, { helpers: helpers, data: context }),
-      'HELP ME MY BOSS @is.a:foo @slave.driver:bar'
-    );
+  it('should note @data references', function() {
+    expectTemplate('{{wycats @is.a @slave.driver}}')
+      .withCompileOptions({ trackIds: true })
+      .withHelpers({
+        wycats: function(passiveVoice, noun, options) {
+          equal(options.ids[0], '@is.a');
+          equal(options.ids[1], '@slave.driver');
+
+          return (
+            'HELP ME MY BOSS ' +
+            options.ids[0] +
+            ':' +
+            passiveVoice +
+            ' ' +
+            options.ids[1] +
+            ':' +
+            noun
+          );
+        }
+      })
+      .withRuntimeOptions({ data: context })
+      .toCompileTo('HELP ME MY BOSS @is.a:foo @slave.driver:bar');
   });
 
   it('should return null for constants', function() {
-    var template = CompilerContext.compile('{{wycats 1 "foo" key=false}}', {
-      trackIds: true
-    });
-
-    var helpers = {
-      wycats: function(passiveVoice, noun, options) {
-        equal(options.ids[0], null);
-        equal(options.ids[1], null);
-        equal(options.hashIds.key, null);
-
-        return (
-          'HELP ME MY BOSS ' +
-          passiveVoice +
-          ' ' +
-          noun +
-          ' ' +
-          options.hash.key
-        );
-      }
-    };
-
-    equals(
-      template(context, { helpers: helpers }),
-      'HELP ME MY BOSS 1 foo false'
-    );
+    expectTemplate('{{wycats 1 "foo" key=false}}')
+      .withCompileOptions({ trackIds: true })
+      .withHelpers({
+        wycats: function(passiveVoice, noun, options) {
+          equal(options.ids[0], null);
+          equal(options.ids[1], null);
+          equal(options.hashIds.key, null);
+
+          return (
+            'HELP ME MY BOSS ' +
+            passiveVoice +
+            ' ' +
+            noun +
+            ' ' +
+            options.hash.key
+          );
+        }
+      })
+      .withInput(context)
+      .toCompileTo('HELP ME MY BOSS 1 foo false');
   });
-  it('should return true for subexpressions', function() {
-    var template = CompilerContext.compile('{{wycats (sub)}}', {
-      trackIds: true
-    });
-
-    var helpers = {
-      sub: function() {
-        return 1;
-      },
-      wycats: function(passiveVoice, options) {
-        equal(options.ids[0], true);
-
-        return 'HELP ME MY BOSS ' + passiveVoice;
-      }
-    };
 
-    equals(template(context, { helpers: helpers }), 'HELP ME MY BOSS 1');
+  it('should return true for subexpressions', function() {
+    expectTemplate('{{wycats (sub)}}')
+      .withCompileOptions({ trackIds: true })
+      .withHelpers({
+        sub: function() {
+          return 1;
+        },
+        wycats: function(passiveVoice, options) {
+          equal(options.ids[0], true);
+
+          return 'HELP ME MY BOSS ' + passiveVoice;
+        }
+      })
+      .withInput(context)
+      .toCompileTo('HELP ME MY BOSS 1');
   });
 
   it('should use block param paths', function() {
-    var template = CompilerContext.compile(
-      '{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}',
-      { trackIds: true }
-    );
-
-    var helpers = {
-      doIt: function(options) {
-        var blockParams = [this.is];
-        blockParams.path = ['zomg'];
-        return options.fn(this, { blockParams: blockParams });
-      },
-      wycats: function(passiveVoice, noun, blah, options) {
-        equal(options.ids[0], 'zomg.a');
-        equal(options.ids[1], 'slave.driver');
-        equal(options.ids[2], 'zomg');
-
-        return (
-          'HELP ME MY BOSS ' +
-          options.ids[0] +
-          ':' +
-          passiveVoice +
-          ' ' +
-          options.ids[1] +
-          ':' +
-          noun
-        );
-      }
-    };
-
-    equals(
-      template(context, { helpers: helpers }),
-      'HELP ME MY BOSS zomg.a:foo slave.driver:bar'
-    );
+    expectTemplate('{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}')
+      .withCompileOptions({ trackIds: true })
+      .withHelpers({
+        doIt: function(options) {
+          var blockParams = [this.is];
+          blockParams.path = ['zomg'];
+          return options.fn(this, { blockParams: blockParams });
+        },
+        wycats: function(passiveVoice, noun, blah, options) {
+          equal(options.ids[0], 'zomg.a');
+          equal(options.ids[1], 'slave.driver');
+          equal(options.ids[2], 'zomg');
+
+          return (
+            'HELP ME MY BOSS ' +
+            options.ids[0] +
+            ':' +
+            passiveVoice +
+            ' ' +
+            options.ids[1] +
+            ':' +
+            noun
+          );
+        }
+      })
+      .withInput(context)
+      .toCompileTo('HELP ME MY BOSS zomg.a:foo slave.driver:bar');
   });
 
   describe('builtin helpers', function() {
@@ -229,119 +197,85 @@ describe('track ids', function() {
 
     describe('#each', function() {
       it('should track contextPath for arrays', function() {
-        var template = CompilerContext.compile(
-          '{{#each array}}{{wycats name}}{{/each}}',
-          { trackIds: true }
-        );
-
-        equals(
-          template(
-            { array: [{ name: 'foo' }, { name: 'bar' }] },
-            { helpers: helpers }
-          ),
-          'foo:array.0\nbar:array.1\n'
-        );
+        expectTemplate('{{#each array}}{{wycats name}}{{/each}}')
+          .withCompileOptions({ trackIds: true })
+          .withHelpers(helpers)
+          .withInput({ array: [{ name: 'foo' }, { name: 'bar' }] })
+          .toCompileTo('foo:array.0\nbar:array.1\n');
       });
+
       it('should track contextPath for keys', function() {
-        var template = CompilerContext.compile(
-          '{{#each object}}{{wycats name}}{{/each}}',
-          { trackIds: true }
-        );
-
-        equals(
-          template(
-            { object: { foo: { name: 'foo' }, bar: { name: 'bar' } } },
-            { helpers: helpers }
-          ),
-          'foo:object.foo\nbar:object.bar\n'
-        );
+        expectTemplate('{{#each object}}{{wycats name}}{{/each}}')
+          .withCompileOptions({ trackIds: true })
+          .withHelpers(helpers)
+          .withInput({ object: { foo: { name: 'foo' }, bar: { name: 'bar' } } })
+          .toCompileTo('foo:object.foo\nbar:object.bar\n');
       });
+
       it('should handle nesting', function() {
-        var template = CompilerContext.compile(
-          '{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}',
-          { trackIds: true }
-        );
-
-        equals(
-          template(
-            { array: [{ name: 'foo' }, { name: 'bar' }] },
-            { helpers: helpers }
-          ),
-          'foo:.array..0\nbar:.array..1\n'
-        );
+        expectTemplate(
+          '{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}'
+        )
+          .withCompileOptions({ trackIds: true })
+          .withHelpers(helpers)
+          .withInput({ array: [{ name: 'foo' }, { name: 'bar' }] })
+          .toCompileTo('foo:.array..0\nbar:.array..1\n');
       });
+
       it('should handle block params', function() {
-        var template = CompilerContext.compile(
-          '{{#each array as |value|}}{{blockParams value.name}}{{/each}}',
-          { trackIds: true }
-        );
-
-        equals(
-          template(
-            { array: [{ name: 'foo' }, { name: 'bar' }] },
-            { helpers: helpers }
-          ),
-          'foo:array.0.name\nbar:array.1.name\n'
-        );
+        expectTemplate(
+          '{{#each array as |value|}}{{blockParams value.name}}{{/each}}'
+        )
+          .withCompileOptions({ trackIds: true })
+          .withHelpers(helpers)
+          .withInput({ array: [{ name: 'foo' }, { name: 'bar' }] })
+          .toCompileTo('foo:array.0.name\nbar:array.1.name\n');
       });
     });
+
     describe('#with', function() {
       it('should track contextPath', function() {
-        var template = CompilerContext.compile(
-          '{{#with field}}{{wycats name}}{{/with}}',
-          { trackIds: true }
-        );
-
-        equals(
-          template({ field: { name: 'foo' } }, { helpers: helpers }),
-          'foo:field\n'
-        );
+        expectTemplate('{{#with field}}{{wycats name}}{{/with}}')
+          .withCompileOptions({ trackIds: true })
+          .withHelpers(helpers)
+          .withInput({ field: { name: 'foo' } })
+          .toCompileTo('foo:field\n');
       });
-      it('should handle nesting', function() {
-        var template = CompilerContext.compile(
-          '{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}',
-          { trackIds: true }
-        );
 
-        equals(
-          template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }),
-          'foo:bat.field\n'
-        );
+      it('should handle nesting', function() {
+        expectTemplate(
+          '{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}'
+        )
+          .withCompileOptions({ trackIds: true })
+          .withHelpers(helpers)
+          .withInput({ bat: { field: { name: 'foo' } } })
+          .toCompileTo('foo:bat.field\n');
       });
     });
+
     describe('#blockHelperMissing', function() {
       it('should track contextPath for arrays', function() {
-        var template = CompilerContext.compile(
-          '{{#field}}{{wycats name}}{{/field}}',
-          { trackIds: true }
-        );
-
-        equals(
-          template({ field: [{ name: 'foo' }] }, { helpers: helpers }),
-          'foo:field.0\n'
-        );
+        expectTemplate('{{#field}}{{wycats name}}{{/field}}')
+          .withCompileOptions({ trackIds: true })
+          .withHelpers(helpers)
+          .withInput({ field: [{ name: 'foo' }] })
+          .toCompileTo('foo:field.0\n');
       });
-      it('should track contextPath for keys', function() {
-        var template = CompilerContext.compile(
-          '{{#field}}{{wycats name}}{{/field}}',
-          { trackIds: true }
-        );
 
-        equals(
-          template({ field: { name: 'foo' } }, { helpers: helpers }),
-          'foo:field\n'
-        );
+      it('should track contextPath for keys', function() {
+        expectTemplate('{{#field}}{{wycats name}}{{/field}}')
+          .withCompileOptions({ trackIds: true })
+          .withHelpers(helpers)
+          .withInput({ field: { name: 'foo' } })
+          .toCompileTo('foo:field\n');
       });
-      it('should handle nesting', function() {
-        var template = CompilerContext.compile(
-          '{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}',
-          { trackIds: true }
-        );
 
-        equals(
-          template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }),
-          'foo:bat.field\n'
-        );
+      it('should handle nesting', function() {
+        expectTemplate('{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}')
+          .withCompileOptions({ trackIds: true })
+          .withHelpers(helpers)
+          .withInput({ bat: { field: { name: 'foo' } } })
+          .toCompileTo('foo:bat.field\n');
       });
     });
   });
diff --git a/spec/utils.js b/spec/utils.js
index 667aa0ed..f4a4e3e4 100644
--- a/spec/utils.js
+++ b/spec/utils.js
@@ -15,11 +15,9 @@ describe('utils', function() {
     it('it should not escape SafeString properties', function() {
       var name = new Handlebars.SafeString('<em>Sean O&#x27;Malley</em>');
 
-      shouldCompileTo(
-        '{{name}}',
-        [{ name: name }],
-        '<em>Sean O&#x27;Malley</em>'
-      );
+      expectTemplate('{{name}}')
+        .withInput({ name: name })
+        .toCompileTo('<em>Sean O&#x27;Malley</em>');
     });
   });
 
diff --git a/spec/whitespace-control.js b/spec/whitespace-control.js
index b1a6db04..f826d95a 100644
--- a/spec/whitespace-control.js
+++ b/spec/whitespace-control.js
@@ -2,125 +2,156 @@ describe('whitespace control', function() {
   it('should strip whitespace around mustache calls', function() {
     var hash = { foo: 'bar<' };
 
-    shouldCompileTo(' {{~foo~}} ', hash, 'bar&lt;');
-    shouldCompileTo(' {{~foo}} ', hash, 'bar&lt; ');
-    shouldCompileTo(' {{foo~}} ', hash, ' bar&lt;');
+    expectTemplate(' {{~foo~}} ')
+      .withInput(hash)
+      .toCompileTo('bar&lt;');
 
-    shouldCompileTo(' {{~&foo~}} ', hash, 'bar<');
-    shouldCompileTo(' {{~{foo}~}} ', hash, 'bar<');
+    expectTemplate(' {{~foo}} ')
+      .withInput(hash)
+      .toCompileTo('bar&lt; ');
 
-    shouldCompileTo('1\n{{foo~}} \n\n 23\n{{bar}}4', {}, '1\n23\n4');
+    expectTemplate(' {{foo~}} ')
+      .withInput(hash)
+      .toCompileTo(' bar&lt;');
+
+    expectTemplate(' {{~&foo~}} ')
+      .withInput(hash)
+      .toCompileTo('bar<');
+
+    expectTemplate(' {{~{foo}~}} ')
+      .withInput(hash)
+      .toCompileTo('bar<');
+
+    expectTemplate('1\n{{foo~}} \n\n 23\n{{bar}}4').toCompileTo('1\n23\n4');
   });
 
   describe('blocks', function() {
     it('should strip whitespace around simple block calls', function() {
       var hash = { foo: 'bar<' };
 
-      shouldCompileTo(' {{~#if foo~}} bar {{~/if~}} ', hash, 'bar');
-      shouldCompileTo(' {{#if foo~}} bar {{/if~}} ', hash, ' bar ');
-      shouldCompileTo(' {{~#if foo}} bar {{~/if}} ', hash, ' bar ');
-      shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, '  bar  ');
+      expectTemplate(' {{~#if foo~}} bar {{~/if~}} ')
+        .withInput(hash)
+        .toCompileTo('bar');
 
-      shouldCompileTo(
-        ' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ',
-        hash,
-        'bar'
-      );
-      shouldCompileTo(
-        ' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ',
-        hash,
-        ' abara '
-      );
+      expectTemplate(' {{#if foo~}} bar {{/if~}} ')
+        .withInput(hash)
+        .toCompileTo(' bar ');
+
+      expectTemplate(' {{~#if foo}} bar {{~/if}} ')
+        .withInput(hash)
+        .toCompileTo(' bar ');
+
+      expectTemplate(' {{#if foo}} bar {{/if}} ')
+        .withInput(hash)
+        .toCompileTo('  bar  ');
+
+      expectTemplate(' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ')
+        .withInput(hash)
+        .toCompileTo('bar');
+
+      expectTemplate(' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ')
+        .withInput(hash)
+        .toCompileTo(' abara ');
     });
+
     it('should strip whitespace around inverse block calls', function() {
-      var hash = {};
+      expectTemplate(' {{~^if foo~}} bar {{~/if~}} ').toCompileTo('bar');
 
-      shouldCompileTo(' {{~^if foo~}} bar {{~/if~}} ', hash, 'bar');
-      shouldCompileTo(' {{^if foo~}} bar {{/if~}} ', hash, ' bar ');
-      shouldCompileTo(' {{~^if foo}} bar {{~/if}} ', hash, ' bar ');
-      shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, '  bar  ');
+      expectTemplate(' {{^if foo~}} bar {{/if~}} ').toCompileTo(' bar ');
 
-      shouldCompileTo(
-        ' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ',
-        hash,
-        'bar'
-      );
+      expectTemplate(' {{~^if foo}} bar {{~/if}} ').toCompileTo(' bar ');
+
+      expectTemplate(' {{^if foo}} bar {{/if}} ').toCompileTo('  bar  ');
+
+      expectTemplate(
+        ' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n '
+      ).toCompileTo('bar');
     });
+
     it('should strip whitespace around complex block calls', function() {
       var hash = { foo: 'bar<' };
 
-      shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'bar');
-      shouldCompileTo('{{#if foo~}} bar {{^~}} baz {{/if}}', hash, 'bar ');
-      shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{~/if}}', hash, ' bar');
-      shouldCompileTo('{{#if foo}} bar {{^~}} baz {{/if}}', hash, ' bar ');
+      expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}')
+        .withInput(hash)
+        .toCompileTo('bar');
 
-      shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'bar');
+      expectTemplate('{{#if foo~}} bar {{^~}} baz {{/if}}')
+        .withInput(hash)
+        .toCompileTo('bar ');
 
-      shouldCompileTo(
-        '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
-        hash,
-        'bar'
-      );
-      shouldCompileTo(
-        '\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
-        hash,
-        'bar<'
+      expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}')
+        .withInput(hash)
+        .toCompileTo(' bar');
+
+      expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}')
+        .withInput(hash)
+        .toCompileTo(' bar ');
+
+      expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}')
+        .withInput(hash)
+        .toCompileTo('bar');
+
+      expectTemplate(
+        '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n'
+      )
+        .withInput(hash)
+        .toCompileTo('bar');
+
+      expectTemplate(
+        '\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n'
+      )
+        .withInput(hash)
+        .toCompileTo('bar<');
+
+      expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').toCompileTo(
+        'baz'
       );
 
-      hash = {};
+      expectTemplate('{{#if foo}} bar {{~^~}} baz {{/if}}').toCompileTo('baz ');
 
-      shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'baz');
-      shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{/if}}', hash, 'baz ');
-      shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{~/if}}', hash, ' baz');
-      shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{/if}}', hash, ' baz ');
+      expectTemplate('{{#if foo~}} bar {{~^}} baz {{~/if}}').toCompileTo(
+        ' baz'
+      );
 
-      shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz');
+      expectTemplate('{{#if foo~}} bar {{~^}} baz {{/if}}').toCompileTo(
+        ' baz '
+      );
 
-      shouldCompileTo(
-        '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
-        hash,
+      expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').toCompileTo(
         'baz'
       );
+
+      expectTemplate(
+        '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n'
+      ).toCompileTo('baz');
     });
   });
 
   it('should strip whitespace around partials', function() {
-    shouldCompileToWithPartials(
-      'foo {{~> dude~}} ',
-      [{}, {}, { dude: 'bar' }],
-      true,
-      'foobar'
-    );
-    shouldCompileToWithPartials(
-      'foo {{> dude~}} ',
-      [{}, {}, { dude: 'bar' }],
-      true,
-      'foo bar'
-    );
-    shouldCompileToWithPartials(
-      'foo {{> dude}} ',
-      [{}, {}, { dude: 'bar' }],
-      true,
-      'foo bar '
-    );
-
-    shouldCompileToWithPartials(
-      'foo\n {{~> dude}} ',
-      [{}, {}, { dude: 'bar' }],
-      true,
-      'foobar'
-    );
-    shouldCompileToWithPartials(
-      'foo\n {{> dude}} ',
-      [{}, {}, { dude: 'bar' }],
-      true,
-      'foo\n bar'
-    );
+    expectTemplate('foo {{~> dude~}} ')
+      .withPartials({ dude: 'bar' })
+      .toCompileTo('foobar');
+
+    expectTemplate('foo {{> dude~}} ')
+      .withPartials({ dude: 'bar' })
+      .toCompileTo('foo bar');
+
+    expectTemplate('foo {{> dude}} ')
+      .withPartials({ dude: 'bar' })
+      .toCompileTo('foo bar ');
+
+    expectTemplate('foo\n {{~> dude}} ')
+      .withPartials({ dude: 'bar' })
+      .toCompileTo('foobar');
+
+    expectTemplate('foo\n {{> dude}} ')
+      .withPartials({ dude: 'bar' })
+      .toCompileTo('foo\n bar');
   });
 
   it('should only strip whitespace once', function() {
-    var hash = { foo: 'bar' };
-
-    shouldCompileTo(' {{~foo~}} {{foo}} {{foo}} ', hash, 'barbar bar ');
+    expectTemplate(' {{~foo~}} {{foo}} {{foo}} ')
+      .withInput({ foo: 'bar' })
+      .toCompileTo('barbar bar ');
   });
 });
