FuncUnit tiered approach allows unit and functional testing in the browser, Rhino, and Selenium. When you scaffolded recipe, it created tests for you. This guide will show you how to:
JavaScriptMVC uses qUnit to test unit functionality (like models and basic plugins). You can run these tests in the browser or Envjs.
cookbook/test/qunit/qunit.js loads qunit and your unit tests. Make sure
you have added recipe_test.js like:
steal
.plugins("funcunit/qunit", "cookbook")
.then("cookbook_test",'recipe_test')Open cookbook/qunit.html. You should see something like:
In a command window type:
> funcunit\envjs cookbook/qunit.htmlThis runs qunit.html in a simulated browser environment. The output should look like:
JavaScriptMVC uses FuncUnit to add browser and selenium-based functional testing to qUnit. You can run tests in the browser or using selenium.
cookbook/test/funcunit/funcunit.js loads funcunit and your functional tests.
Make sure you have added recipe_controller_test.js like:
steal
.plugins("funcunit")
.then("cookbook_test",'recipe_controller_test')
Open cookbook/funcunit.html. You should see something like:
In a command window type:
> funcunit\envjs cookbook\funcunit.htmlThis should open Firefox and IE if you are using Windows. The results of the
test should look like:
If you are having trouble running the tests in Internet Explorer, you need to change a few settings in the browser. Please see the FuncUnit documentation for troubleshooting help.
FuncUnit adds very little to qUnit, so the best place to start understanding qUnit is its own documentation. FuncUnit / JavaScriptMVC just adds a way to:
Here's how it works ...
cookbook/qunit.html loads steal.js and tells it to load:
cookbook/test/qunit/qunit.js with the following script tag:
<script type='text/javascript'
src='../steal/steal.js?steal[app]=cookbook/test/qunit'>
</script> cookbook/test/qunit/cookbook_test.js
tests are added to be run by qunit.
When the page is run in Envjs, qUnit does the same 4 steps, but reports the messages on the command line.
As an example of a test, let look at how the findAll test works:
//creates a test
test("findAll", function(){
//prevents the next test from running
stop(2000);
//requests recipes
Cookbook.Models.Recipe.findAll({}, function(recipes){
//makes sure we have something
ok(recipes)
//makes sure we have at least 1 recipe
ok(recipes.length)
//makes sure a recipe looks right
ok(recipes[0].name)
ok(recipes[0].description)
//allows the next test to start
start()
});
})
FuncUnit adds to qUnit the ability to open another page, in this case
cookbook/cookbook.html, perform actions on it, and
get information from it.
The cookbook/funcunit.html page
works just like the qunit.html page except the 'funcunit' plugin is loaded which
provides FuncUnit. FuncUnit is aliased to "S" to highlight the similarity between its API
and jQuery's API.
Let take a quick look at a FuncUnit test:
test("create recipes", function(){
//type Ice in the name field
S("[name=name]").type("Ice")
//type Cold Water in the description field
S("[name=description]").type("Cold Water")
//click the submit button
S("[type=submit]").click()
//wait until the 2nd recipe exists
S('.recipe:nth-child(2)').exists()
//Gets the text of the first td
S('.recipe:nth-child(2) td:first').text(function(text){
//checks taht it has ice
ok(text.match(/Ice/), "Typed Ice");
});
})Wait ... why is getting the text passed a function?
Functional tests are largely many asynchronous actions (clicks and keypresses) with relatively few checks/assertions. FuncUnit's goal is to provide as readable and linear syntax as possible. FuncUnit statements are actually stored and then run asynchronously. This requires that getting a value from the page happens in a callback function.
For more information on FuncUnit, read its documentation
In the Creating Cookbook section of the Getting Started guide, we added an isTasty function to be shown. Lets see how we could unit test that functionality.
At the end of recipe_test.js we'll add code that
creates two recipe instances and checks if they are tasty.
test("isTasty", function(){
var Recipe = Cookbook.Models.Recipe,
r1 = new Recipe({name: "tea",
description: "leaves and water"}),
r2 = new Recipe({name: "mushroom soup",
description: "mushrooms and water"});
ok(r1.isTasty(), "tea is tasty")
ok(!r2.isTasty(), "mushroom soup is not tasty")
})Next, learn how to Compress Cookbook.