-
Notifications
You must be signed in to change notification settings - Fork 55
Mock Objects
Mock objects are used to eliminate test dependencies on other objects. In complex software systems, there's often multiple
object that have dependence on one another to do their job. Perhaps part of your code relies on the XMLHttpRequest
object to get more information; if you're running the test without a network connection, you can't really be sure if the test
is failing because of your error or because the network connection is down. In reality, you just want to be sure that the correct
data was passed to the open()
and send()
methods because you can assume that, after that point,
the XMLHttpRequest
object works as expected. This is the perfect case for using a mock object.
To create a mock object, use the YUITest.Mock()
method to create a new object and then use YUITest.Mock.expect()
to define expectations for that object. Expectations define which methods you're expecting to call, what the arguments should be,
and what the expected result is. When you believe all of the appropriate methods have been called, you call YUITest.Mock.verify()
on the mock object to check that everything happened as it should. For example:
//code being tested
function logToServer(message, xhr){
xhr.open("get", "/log.php?msg=" + encodeURIComponent(message), true);
xhr.send(null);
}
//test case for testing the above function
var testCase = new YUITest.TestCase({
name: "logToServer Tests",
testPassingDataToXhr : function () {
var mockXhr = YUITest.Mock();
//I expect the open() method to be called with the given arguments
YUITest.Mock.expect(mockXhr, {
method: "open",
args: ["get", "/log.php?msg=hi", true]
});
//I expect the send() method to be called with the given arguments
YUITest.Mock.expect(mockXhr, {
method: "send",
args: [null]
});
//now call the function
logToServer("hi", mockXhr);
//verify the expectations were met
YUITest.Mock.verify(mockXhr);
}
});
In this code, a mock XMLHttpRequest
object is created to aid in testing. The mock object defines two
expectations: that the open()
method will be called with a given set of arguments and that the send()
method will be called with a given set of arguments. This is done by using YUITest.Mock.expect()
and passing in the
mock object as well as some information about the expectation. The method
property indicates the method name
that will be called and the args
property is an array of arguments that should be passed into the method. Each
argument is compared against the actual arguments using the identically equal (===
) operator, and if any of the
arguments doesn't match, an assertion failure is thrown when the method is called (it "fails fast" to allow easier debugging).
The call to YUITest.Mock.verify()
is the final step in making sure that all expectations have been met. It's at this stage
that the mock object checks to see that all methods have been called. If open()
was called but send()
was not, then an assertion failure is thrown and the test fails. It's very important to call YUITest.Mock.verify()
to test
all expectations; failing to do so can lead to false passes when the test should actually fail.
In order to use mock objects, your code must be able to swap in and out objects that it uses. For example, a hardcoded
reference to XMLHttpRequest
in your code would prevent you from using a mock object in its place. It's sometimes
necessary to refactor code in such a way that referenced objects are passed in rather than hardcoded so that mock objects
can be used.
Note that you can use assertions and mock objects together; either will correctly indicate a test failure.
There may be times when you don't necessarily care about a specific argument's value. Since you must always specify the correct number of arguments being passed in, you still need to indicate that an argument is expected. There are several special values you can use as placeholders for real values. These values do a minimum amount of data validation:
-
YUITest.Mock.Value.Any
- any value is valid regardless of type. -
YUITest.Mock.Value.String
- any string value is valid. -
YUITest.Mock.Value.Number
- any number value is valid. -
YUITest.Mock.Value.Boolean
- any Boolean value is valid. -
YUITest.Mock.Value.Object
- any non-null
object value is valid. -
YUITest.Mock.Value.Function
- any function value is valid.
Each of these special values can be used in the args
property of an expectation, such as:
YUITest.Mock.expect(mockXhr, {
method: "open",
args: [YUITest.Mock.Value.String, "/log.php?msg=hi", YUITest.Mock.Value.Boolean]
});
The expecation here will allow any string value as the first argument and any Boolean value as the last argument.
These special values should be used with care as they can let invalid values through if they are too general. The
YUITest.Mock.Value.Any
special value should be used only if you're absolutely sure that the argument doesn't
matter.
Since it's not possible to create property getters and setters in all JavaScript environments, creating a true cross-browser property
expectation isn't feasible. YUI Test mock objects allow you to specify a property name and it's expected value when
YUITest.Mock.verify()
is called. This isn't a true property expectation but rather an expectation that the property
will have a certain value at the end of the test. You can specify a property expectation like this:
//expect that the status property will be set to 404
YUITest.Mock.expect(mockXhr, {
property: "status",
value: 404
});
This example indicates that the status
property of the mock object should be set to 404 before
the test is completed. When YUITest.Mock.verify()
is called on mockXhr
, it will check
the property and throw an assertion failure if it has not been set appropriately.