Xray-specs is a project mainly written in ..., it's free.
Javascript mocking framework
Simple mocking and stub library. I decided to write my own library because I couldn't find support for the following:
See below for more information about these topics.
Full API documentation coming soon...
Mocks are used to help keep unit tests focused solely on one section of code. They do this by replacing any dependencies with intelligent objects that can record any interaction with the object under test.
The following code will create a new object called collaborator
within example
. The third parameter is used to specify the functions that the mock will include, each of these will be replaced by a stub allowing for interactions to be recorded.
example = {
sut: {}
};
xray_specs.mock(example, 'collaborator', {
some_method: {},
another_method: {}
});
This can then be used by telling the mock to expect a certain method call. Finally, calling verify will return true or false depending on whether all expectations have been met. In this case it will return true because some_method
has been called.
namespace.collaborator.expects("some_method");
namespace.collaborator.some_method();
assertTrue(namespace.collaborator.verify());
There are a few different ways to set up mock objects.
Create a mock for an object that doesn't currently exist.
example = {};
xray_specs.mock(example, 'collaborator', {
some_method: {},
another_method: {}
});
Create a mock based on an existing object.
example = {
collaborator: {
some_method: {},
another_method: {}
}
};
xray_specs.mock(example, 'collaborator');
Create a mock that inherits an existing object and adds additional methods.
example = {
collaborator: {
some_method: {},
another_method: {}
}
};
xray_specs.mock(example, 'collaborator', {
do_something: {}
});
You can also create an empty mock and dynamically add new stubbed methods as expectations are called (see below for more details).
example = {};
xray_specs.mock(example, 'collaborator');
...
namespace.collaborator.expects("a_new_method");
Mocks need to be reset after use so that normal functionality is not permanently changed
namespace.collaborator.reset()
I decided to make this a manual call because automatically resetting on verification can lead to issues when sharing mocks across tests.
The most basic possible expectation is
namespace.collaborator.expects("some_method");
which simply states that it expects some_method
to be called at least once with any arguments.
You can also specify the number of calls that a method should receive.
namespace.collaborator.expects("some_method")
.to_be_called.times(3);
namespace.collaborator.expects("some_method")
.to_be_called.at_least(2);
namespace.collaborator.expects("some_method")
.to_be_called.at_most(3);
namespace.collaborator.expects("some_method")
.to_be_called.between(3, 5);
Arguments that are received can also be specified.
matching
will only pass if all the arguments match exactly.
namespace.collaborator.expects("some_method")
.with_args.matching("hello", "world");
namespace.collaborator.some_method("hello", "world") // PASS
namespace.collaborator.some_method("hello") // FAIL
including
is less strict and will pass if any arguments match the expectation.
namespace.collaborator.expects("some_method")
.with_args.matching("hello", "world");
namespace.collaborator.some_method("hello", "world") // PASS
namespace.collaborator.some_method("hello") // PASS
namespace.collaborator.some_method() // FAIL
These methods can be chained on to call expectations, e.g.
namespace.collaborator.expects("some_method")
.to_be_called.at_least(3)
.with_args.matching("hello", "world");
matching
and including
will pass if their expectations are met at least once. So the following would pass verification for the above expectations.
namespace.collaborator.some_method("hello", "world");
namespace.collaborator.some_method();
namespace.collaborator.some_method();
If you want to check the same arguments are supplied every time then you can use the always_matching
and always_including
methods. Both work in the same way as the standard ones, but will only pass if their expectations are matched for each call.
It is also possible to check parameters by type. For example, if you want to make sure that a callback function is passed, but don't want to tie in a specific function then you could do the following:
namespace.collaborator.expects("some_method")
.with_args.matching("type::function");
namespace.collaborator.some_method(function() {
// I'm an anonymous function!
});
The type check is initiated is the string "type::" is found. Any valid javascript type can then be supplied. This is an initial attempt at implementing type-checking and may well change, use with care.
You can also create custom expectations to make your tests more readable.
var hello_three_times = function() {
this.to_be_called.times(3).with_args.matching("hello");
};
namespace.collaborator.expectations.called_with_hello_3_times = hello_three_times;
...
namespace.collaborator.expects('some_method')
.called_with_hello_3_times();
If you need the mock to return a value after it's called you can chain a call at the end of the expectation list
namespace.collaborator.expects('some_method')
.and_returns("hello");
var return_value = namespace.collaborator.some_method();
assertEquals("hello", return_value); // PASS
The mocks in xray_specs use stubs to replace each of the methods in the target object. If you don't need to create an entire mock object you can also stub single function directly and monitor their behaviour in a similar way.
example = {
do_something: function() {}
};
xray_specs.stub(sut, "some_method");
example.some_method("hello world")
assertTrue(example.some_method.called_with("hello world")); // PASS
Create a stub function on an object
example = {};
xray_specs.stub(sut, "some_method");
Replace an existing function with a stub:
example = {
another_method: function() {
}
};
xray_specs.stub(sut, "another_method");
You can also create anonymous stubs that can be passed as callbacks, etc.
var anonStub = xray_specs.stub();
As with mocks, stubs must be reset after use to ensure original state is maintained.
example.another_method.reset();
Monitoring calls made to a stub is done in a very similar fashion to mock objects.
example.some_method();
example.some_method();
example.some_method();
example.some_method.was_called; // TRUE
example.some_method.called_at_least(3); //TRUE
example.some_method.called_at_most(5); // TRUE
example.some_method.called_exactly(3); // TRUE
Inspecting parameters also works in the same way as for mocks.
called_with
passes if any arguments match.
example.some_method("bread", "milk", "eggs");
assertTrue(example.some_method.called_with("eggs")); // PASS
assertTrue(example.some_method.called_with("bread", "milk", "eggs")); // PASS
assertTrue(example.some_method.called_with("wood", "milk", "bread")); // PASS
called_with_exactly
will only pass if all arguments match.
example.some_method("bread", "milk", "eggs");
assertFalse(example.some_method.called_with_exactly("bread", "milk", "eggs")); // PASS
assertFalse(example.some_method.called_with_exactly("eggs", "milk", "bread")); // PASS
assertFalse(example.some_method.called_with_exactly('bread', 'milk', 'flour')); // FAIL
always_called_with
and always_called_with_exactly
can be used to verify that methods are called with the expected parameters for each call.
Return values can be set using the returns
method.
sut.some_method.returns("hello");
assertEquals("hello", sut.some_method()); // PASS