When is spying on method useful during unit testing? - javascript

With Jasmine it is possible to spyOn methods, but I'm not clear on when would it be actually useful. My understanding is that unit tests should not be concerned with implementation details and testing if method is called would be implementation detail.
One place I might think of is spying on scope.$broadcast (Angular) etc but then again this would be implementation detail and not sure if unit tests should even bother with how the code works, as long as it gives expected result.
Obviously there are good reasons to use spyOn so what would be good place to use it?

The spyOn you describe is more commonly known in testing as a MOCK, although to be more clear it allows for 2 operations:
Create a new implementation for a method via createSpy (this is the classical mock)
Instrument an existing method via spyOn (this allows you to see if the method was called and with what args, the return value etc.)
Mocking in probably the most used technique in unit testing. When you are testing a unit of code, you'll often find that there are dependencies to other units of code, and those dependencies have their own dependencies etc. If you try to test everything you'll end up with an module / UI test, which are expensive and difficult to maintain (they are still valuable, but you want as few of those as possible)
This is where mocking comes in. Imagine your unit calls to a REST service for some data. You don't want to take a dependency on a service in your unit test. So you mock the method that calls the service and you provide your own implementation that simply returns some data. Want to check that your unit handles REST errors? Have your mock return an error. etc.
It can sometimes be useful to know if your code actually calls another unit of code - imagine that you want to make sure your code correctly calls a logging module. Just mock (spyOn) that logging module and assert that it was called X number of times with the proper parameters.

You can spy functions and then you will be able to assert a couple of things
about it. You can check if it was called, what parameters it had, if it
returned something or even how many times it was called!
Spies are highly useful when writing tests, so I am going to explain how to
use the most common of them here.
// This is our SUT (Subject under test)
function Post(rest) {
this.rest = rest;
rest.init();
}
We have here our SUT which is a Post constructor. It uses a RestService to
fetch its stuff. Our Post will delegate all the Rest work to the RestService
which will be initialized when we create a new Post object. Let’s start testing
it step by step:
`describe('Posts', function() {
var rest, post;
beforeEach(function() {
rest = new RestService();
post = new Post(rest);
});
});`
Nothing new here. Since we are going to need both instances in every test,
we put the initialization on a beforeEach so we will have a new instance every
time.
Upon Post creation, we initialize the RestService. We want to test that, how
can we do that?:
`it('will initialize the rest service upon creation', function() {
spyOn(rest, 'init');
post = new Post(rest);
expect(rest.init).toHaveBeenCalled();
});`
We want to make sure that init on rest is being called when we create a new
Post object. For that we use the jasmine spyOn function. The first parameter is
the object we want to put the spy and the second parameter is a string which
represent the function to spy. In this case we want to spy the function init
on the spy object. Then we just need to create a new Post object that will call
that init function. The final part is to assert that rest.init have been
called. Easy right? Something important here is that the when you spy a
function, the real function is never called. So here rest.init doesn’t actually
run.

Related

How do you deal with a fluctuating unit test case with Jasmine?

I am writing unit test cases using Jasmine for Angular 13 project. there is a test case which passes sometimes and fails sometimes. I presume this happens because of order of the tests execution. Any idea how to deal with it?
An error was thrown in afterall
By default the tests run in a random order each time. There is a seed value so that you can recreate the order. You can read on how to approach that in this answer.
Once you have yours executing where it fails each time you will easily know if any of the following has actually fixed your issue.
You can also check for anywhere you are subscribing to something - every time you subscribe in your test you need to make sure that it gets unsubscribed at the end of the test. To do this you can put .pipe(take(1)) or capture the subscription object and call unsubscribe on it.
const sub = someService.callObservable().subscribe();
// verify what you need to
sub.unsubscribe();
A third concept to look at - any variables you have defined above the beforeEach should be set to a new value in the beforeEach. Otherwise you will have the same objects reused between tests and that can lead to issues.

Jasmine testing stateful service

I am new to Jasmine testing and looking for a "best practice" for unit testing a stateful AngularJS service. Most tutorials I could find focus on test cases that run atomic calls to stateless services.
This matches nicely the Jasmine syntax
it("should do something", function(){ expect(target.doSomething()).toBe... })
However, I found no obvious way to extend this pattern to a test case that involves multiple calls to one or several of the service's functions.
Let's imagine a service like this one:
angular.module("someModule").factory("someService", function(){
return {
enqueue: function(item){
// Add item to some queue
}
}
});
For such a service, it makes sense to test that sequential calls to enqueue() process the items in the correct order. This involves writing a test case that calls enqueue() multiple times and checks the end result (which obviously cannot be achieved with a service as simple as the one above, but this is not the point...)
What doesn't work:
describe("Some service", function(){
// Initialization omitted for simplicity
it("should accept the first call", function() {
expect(someService.enqueue(one)).toBe... // whatever is correct
});
it("should accept the second call", function() {
expect(someService.enqueue(two)).toBe... // whatever is correct
});
it("should process items in the correct order", function() {
// whatever can be used to test this
});
});
The code above (which actually defines not one but three test cases) fails randomly as the three test cases are executed... just as randomly.
A poster in this thread suggested that splitting the code into several describe blocks would have these to be executed in the given order, but again this seems to be different from one Jasmine version to another (according to other posters in same thread). Moreover, executing suites and tests in a random order seems to be the intended way; even if it was possible, by setup, to override this behaviour, that would probably NOT be the right way to go.
Thus it seems that the only correct way to test a multi-call scenario is to make it ONE test case, like this:
describe(("Some service", function(){
// Initialization omitted for simplicity
it("should work in my complex scenario", function(){
expect(someService.enqueue(one)).toBe... // whatever is correct
expect(someService.enqueue(two)).toBe... // whatever is correct
expect(/* whatever is necessary to ensure the order is correct */);
});
});
While technical-wise this seems the logical way to go (after all, a complex scenario is one test case not three), the Jasmine "description + code" pattern seems disturbed in this implementation as:
There is no way to associate a message to each "substep" that can fail within the test case;
The description for the single "it" is inevitably bulky, like in example above, to really say something useful about a complex scenario.
This makes me wonder whether this is the only correct solution (or ist it?) to this kind of testing needs. Again I am especially interested in "doing it the right way" rather than using some kind of hack that would make it work... where it should not.
Sorry no code for this... Not sure it needs, I think you just need to adjust your expectations of your tests.
For the general rule of testing you don't really care how external dependencies handle the service, you can't control that. You want to test what you think the expected results are going to be for your service.
For your example you'll just want to invoke the dependencies for your service and call the function and test what the expected results are from calling the enqueue function. If it returns a promise, check success and error. It calls an API check that and so on.
If you want to see how the external dependencies use your service, you'll test it on those dependencies tests.
For example you have a controller that invokes enqueue. In the test you'll have to inject your provider (the service). And handle the expectations.

When using Sinon.js to create mocks, what does mock.restore() actually do?

I've started using Sinon.js to mock a MongoDB library in a Mocha test suite. I'm confused as to why mock.restore() in my afterEach blocks do not actually clear out the mocks & assertions I've set up in other tests. Example:
mockedMongo.expects('updateCustomer').once();
mockedMongo.restore();
mockedMongo.expects('updateCustomer').never();
mockedMongo.verify(); // error here
The last line will throw an Expected updateCustomer([...]) once (never called) ExpectationError. In the documentation it says that mock.restore() "Restores all mocked methods". I'm trying to figure out what that actually means, since it doesn't clear out my previous expectation, even when it seems that I've overwritten the mock on that method with something else. Thoughts?
Summary
If any methods have been wrapped in a proxy by the mock, restore() returns them to their original state. That's all it does.
Details
Looking at the source gives the following info:
calling expects() sets up a proxy for the method if no expectations have been set on it yet, and then adds an expectation
calling verify() cycles the expectations on the proxies and verifies each, then calls restore()
restore() cycles the proxies and restores the original methods
All restore() does is remove any proxies added by expects(), it doesn't affect the expectations stored by the mock.
So for each line in your example code:
create proxy for updateCustomer and add expectation of once
restore original updateCustomer
add expectation of never to updateCustomer
cycle the two expectations on updateCustomer and record that once fails, call restore(), then report that once failed

test *initialization* of factory or service in jasmine

I'm using Angular, and we have a factory which does some initialization of restangular defaults. It's responsible for adding a number of hooks into restangular paths for later.
I want to test the logic of this factory, but the problem is that most of the hooks are set before the factory is returned, their done once at initialization. As such I don't know how to test the logic. I can't add a spy, because by the time I have a factory object it's already been initialized and it's too late to test that init logic.
I can test that specific paths received the expected hooks, but I feel like that not a proper unit level test. Since were adding a number of general helper methods to every one of our paths I would really like to test in isolation that each general helper function was added done, in a way that is not depending on any of the other hooks we set specific to individual paths.
Is there a good way to plug in to Angular and test the init logic that occurs before a factory is returned? I would like to either modify a default array (the one that associates paths to specific hooks), or to be able to call helper functions in isolation.

sinon.stub() vs sinon.sandbox.stub()?

Using sinon and sinon-qunit in our front end unit tests, and I'm struggling to understand the difference in these methods. We are using sinon.sandbox.stub() (literally that is the function, we do not create a sandbox) and these stubs are apparently restored after each test automatically. I just don't see this anywhere in the documentation.
I wouldn't think that this method exists, I would think you would need to explicitly create a sandbox using sinon.sandbox.create(). On that sandbox object you would call the stub function, i.e. mySandbox.stub(), not "sinon.sandbox.stub()".
Could anyone help me understand?
Stubs - Sinon.JS
sinon.stub(); read about from here
Sandboxes - Sinon.JS
sandbox.stub(); read detail from here
Works almost exactly like sinon.stub, only also adds the returned stub to the internal collection of fakes for easy restoring through sandbox.restore().
The sandbox stub method can also be used to stub any kind of property. This is useful if you need to override an object’s property for the duration of a test, and have it restored when the test completes

Categories