Sinon.js and Backbone - Testing for view method invocation - unit-testing

Im working on a test suite for an existing Backbone application using Jasmine and Sinon and I am testing that my router performs the correct actions on a certain route. Here's the actual route function:
favourites: function()
{
//Dont re-initialize the favourites view as there is no need.
//Instead, just render the favourite movies
if ( ! this.favMoviesView)
{
this.favMoviesView = new cinephile.Views.FavouriteMoviesView({
collection: cinephile.favouriteMovies
});
}
else
{
this.favMoviesView.renderFavourites();
}
$('#content').html(this.favMoviesView.el);
},
In my test suite I want to assert that when navigating to to the favourites route this.favMoviesView will be created once and then, if it exists will not re-initialize but instead just call this.favMoviesView.renderFavourites() which is a method that iterates over the view's collection.
Here's my test spec:
describe('cinephile.Routers.CinephileRouter', function () {
beforeEach(function () {
this.router = new cinephile.Routers.CinephileRouter();
this.routeSpy = sinon.spy();
try
{
Backbone.history.start({ silent : true });
}
catch(e) {}
this.router.navigate('elsewhere');
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView')
.returns(new Backbone.View());
});
afterEach(function () {
this.favouritesViewStub.restore();
});
describe('Favourites Route', function() {
it('should load the favourites on /favourites', function () {
this.router.bind('route:favourites', this.routeSpy);
this.router.navigate('favourites', true);
expect(this.routeSpy.calledOnce).toBeTruthy();
expect(this.routeSpy.calledWith()).toBeTruthy();
});
it('creates a favourites view if one doesn\'t exist', function () {
this.router.favourites();
expect(this.favouritesViewStub.calledOnce).toBeTruthy();
});
it('Reuses the favourites view if one does exist and reset it\'s collection', function () {
this.router.favourites();
this.router.favourites();
expect(this.favouritesViewStub.calledOnce).toBeTruthy();
expect(this.favouritesViewStub.renderFavourites).toHaveBeenCalledTwice();
});
});
});
My first two tests pass and I believe them to correctly describe the favourites method in my router. The third test is the the one giving me problems. As I understand it, because I am testing my router and NOT the FavouriteMoviesView I should be stubbing out the view to keep the test isolated. If that is the correct assumption, my issue becomes that the stub won't have a renderFavourites method as it is a stubbed out Backbone.View().
How can I fix this particular problem and if you are so inclined, I believe I'm missing something conceptual so feel free to explain what it is that I'm not understanding.
Cheers.

You problem is that you want to mock something inside a mock function. What I would suggest is that instead of this...
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView').returns(new Backbone.View());
...have this:
var StubView = Backbone.View.extend({
renderFavourites: sinon.stub()
});
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView').returns(new StubView());
Now your View "constructor" will return a StubView, which has the method you are calling stubbed out. So this Backbone View with the stubbed out method will be placed in the router.favMoviesView -property. The favouritesViewStub property still contains just the "constructor" -function, so you can't access this stubbed method from there. This is why you haveto change this from the last test:
expect(this.favouritesViewStub.renderFavourites).toHaveBeenCalledTwice();
to something like this:
expect(this.router.favMoviesView.renderFavourites).toHaveBeenCalledTwice();
This way you will actually check if the router's copy of the view has had the method called twice.
Hope this works for you, comment if it doesn't! I didn't test this out, so there could be some problems, but I'm sure the logic behind works.

Related

How to get the Ref value in a test with Vue 3 Composition API

I have a v-if rendering in my component that is link to a ref in the setup method.
How can I test the rendrering in my testing script.
Example:
In the component:
<div v-if="isCovid" class="covid"/>
setup() {
const isCovid = ref(true);
}
In the test component (I try this but dont work, the test received true):
it('do not render covid waring when its false', () => {
const wrapper = mount(TicketHeader,{
data(){
return { isCovid: false}
}
});
expect(wrapper.find('.covid').exists()).toBe(false)
});
If you are using the setup() method this is what counts. In the test you then use data() which AFAIK doesn't work if you use setup().
One small side note there, do you return isCovid so it is accessible in the
template?
I don't think you can modify a ref that you create in setup(). You'll probably need to provide something as a prop which you then access there. I just found this with an example:
https://lmiller1990.github.io/vue-testing-handbook/composition-api.html#the-component

How to test VueRouter's beforeRouteEnter using '#vue/test-utils'?

I'm trying to test my 'Container' component which handles a forms logic. It is using vue-router and the vuex store to dispatch actions to get a forms details.
I have the following unit code which isn't working as intended:
it('On route enter, it should dispatch an action to fetch form details', () => {
const getFormDetails = sinon.stub();
const store = new Vuex.Store({
actions: { getFormDetails }
});
const wrapper = shallowMount(MyComponent, { store });
wrapper.vm.$options.beforeRouteEnter[0]();
expect(getFormDetails.called).to.be.true;
});
With the following component (stripped of everything because I don't think its relevant (hopefully):
export default {
async beforeRouteEnter(to, from, next) {
await store.dispatch('getFormDetails');
next();
}
};
I get the following assertion error:
AssertionError: expected false to be true
I'm guessing it is because I am not mounting the router in my test along with a localVue. I tried following the steps but I couldn't seem to get it to invoke the beforeRouteEnter.
Ideally, I would love to inject the router with a starting path and have different tests on route changes. For my use case, I would like to inject different props/dispatch different actions based on the component based on the path of the router.
I'm very new to Vue, so apologies if I'm missing something super obvious and thank you in advance for any help! 🙇🏽
See this doc: https://lmiller1990.github.io/vue-testing-handbook/vue-router.html#component-guards
Based on the doc, your test should look like this:
it('On route enter, it should dispatch an action to fetch form details', async () => {
const getFormDetails = sinon.stub();
const store = new Vuex.Store({
actions: { getFormDetails }
});
const wrapper = shallowMount(MyComponent, { store });
const next = sinon.stub()
MyComponent.beforeRouteEnter.call(wrapper.vm, undefined, undefined, next)
await wrapper.vm.$nextTick()
expect(getFormDetails.called).to.be.true;
expect(next.called).to.be.true
});
A common pattern with beforeRouteEnter is to call methods directly at the instantiated vm instance. The documentation states:
The beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
However, you can access the instance by passing a callback to next. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument:
beforeRouteEnter (to, from, next) {
next(vm => {
// access to component instance via `vm`
})
}
This is why simply creating a stub or mock callback of next does not work in this case. I solved the problem by using the following parameter for next:
// mount the component
const wrapper = mount(Component, {});
// call the navigation guard manually
Component.beforeRouteEnter.call(wrapper.vm, undefined, undefined, (c) => c(wrapper.vm));
// await
await wrapper.vm.$nextTick();

Angular2 testing logic before .subscribe

I am currently writing unit tests for one of my components. In particular, I have login(): void function. Here's the simplified logic:
login(): void {
this.showSpinner = true;
this.userService.login(loginData)
.subscribe(result => {
this.showSpinner = false;
}
)
}
I am struggling to write a test that checks that showSpinner property gets set to true before calling the userService.login.
Here's my test:
it('should display the spinner when the form is being saved',
inject([TestComponentBuilder], fakeAsync((tcb: any) => {
createComponent(tcb).then((fixture:ComponentFixture<any>) => {
fixture.componentInstance.login();
expect(fixture.componentInstance.showSpinner).toBe(true);
tick();
});
})));
});
And this test fails, because .subscribe gets resolved / run immediately (i tried commenting out this.showSpinner = false in my component, and the test passed).
In my userService mock, I have the following, for the login method mock:
this.loginSpy = this.spy('login').andReturn(Observable.of(this));
Where this is mockUserService.
I am confident that I am mocking userService and specifically the login method on the userService correctly, as I have other tests for this component that behave correctly.
I have also tried returning Observable.of(this).delay(1) from my spy and then calling tick(1) in my test. However that results in inconsistent behaviour in that sometimes my tests pass, but other times i get an error saying:
Error: 1 periodic timer(s) still in the queue.
How can I test the logic that precedes .subscribe()?
After more consideration I have realized that my current code does not abide by the single responsibility principle. This thought came from the fact that everyone is always repeating that you should "Refactor hard to test code".
With that in mind, I have moved all the logic that needed to be done before the call to userService.login is being made - into its own separate function. Which essentially results in:
login():void {
this.userService.login(this.loginData)
.subscribe(result => {
this.showSpinner = false;
});
}
formSubmit(): void {
this.showSpinner = true;
this.login();
}
This logic is now much easier to test.
HOWEVER we need to remember to add a spy on our login() method when we are testing formSubmit(), as if we don't, formSubmit() will simply make a call to login(), which will again complete synchronously and we will have the same problem. So my new and final test for this feature is:
it('should display the spinner when the form is being saved',
inject([TestComponentBuilder], fakeAsync((tcb: any) => {
createComponent(tcb).then((fixture:ComponentFixture<any>) => {
var loginSpy = spyOn(fixture.componentInstance, 'login');
fixture.componentInstance.formSubmit();
expect(fixture.componentInstance.showSpinner).toBe(true);
});
})));
});

How do you mock a service in AngularJS when unit testing with jasmine?

Let's say I have a service shop that depends on two stateful services schedule and warehouse. How do I inject different versions of schedule and warehose into shop for unit testing?
Here's my service:
angular.module('myModule').service('shop', function(schedule, warehouse) {
return {
canSellSweets : function(numRequiredSweets){
return schedule.isShopOpen()
&& (warehouse.numAvailableSweets() > numRequiredSweets);
}
}
});
Here are my mocks:
var mockSchedule = {
isShopOpen : function() {return true}
}
var mockWarehouse = {
numAvailableSweets: function(){return 10};
}
Here are my tests:
expect(shop.canSellSweets(5)).toBe(true);
expect(shop.canSellSweets(20)).toBe(false);
beforeEach(function () {
module(function ($provide) {
$provide.value('schedule', mockSchedule);
});
});
Module is a function provided by the angular-mocks module. If you pass in a string argument a module with the corresponding name is loaded and all providers, controllers, services, etc are available for the spec. Generally they are loaded using the inject function. If you pass in a callback function it will be invoked using Angular's $injector service. This service then looks at the arguments passed to the callback function and tries to infer what dependencies should be passed into the callback.
Improving upon Atilla's answer and in direct answer to KevSheedy's comment, in the context of module('myApplicationModule') you would do the following:
beforeEach(module('myApplicationModule', function ($provide) {
$provide.value('schedule', mockSchedule);
}));
With CoffeeScript I run in some issues so I use null at the end:
beforeEach ->
module ($provide) ->
$provide.value 'someService',
mockyStuff:
value : 'AWESOME'
null
You can look here for more info
https://docs.angularjs.org/guide/services#unit-testing
You want to utilize the $provide service. In your case
$provide.value('schedule', mockSchedule);
As you are using jasmine, there is an alternative way to mock the calls with jasmine's spies (https://jasmine.github.io/2.0/introduction.html#section-Spies).
Using these you can be targeted with your function calls, and allow call throughs to the original object if required. It avoids clogging up the top of your test file with $provide and mock implementations.
In the beforeEach of your test I would have something like:
var mySchedule, myWarehouse;
beforeEach(inject(function(schedule, warehouse) {
mySchedule = schedule;
myWarehouse = warehouse;
spyOn(mySchedule, 'isShopOpen').and.callFake(function() {
return true;
});
spyOn(myWarehouse, 'numAvailableSweets').and.callFake(function() {
return 10;
});
}));
and this should work in similar fashion to the $provide mechanism, noting you have to provide local instances of the injected variables to spy on.
I recently released ngImprovedTesting module that should make mock testing in AngularJS way easier.
In your example you would only have to replace in your Jasmine test the ...
beforeEach(module('myModule'));
... with ...
beforeEach(ModuleBuilder.forModule('myModule').serviceWithMocks('shop').build());
For more information about ngImprovedTesting check out its introductory blog post:
http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/
It is simpler to put the mock on the module like this:
beforeEach(function () {
module('myApp');
module({
schedule: mockSchedule,
warehouse: mockWarehouse
}
});
});
you can use injection to get reference to these mocks for pre test manipulations :
var mockSchedule;
var mockWarehouse;
beforeEach(inject(function (_schedule_, _warehouse_) {
mockSchedule = _schedule_;
mockWarehouse = _warehouse_;
}));
I hope my answer is not that useless, but you can mock services by $provide.service
beforeEach(() => {
angular.mock.module(
'yourModule',
($provide) => {
$provide.service('yourService', function() {
return something;
});
}
);
});

Testing a AngularJS services with dependencies

I have som issues testing a simple service in AngularJS, which depends on another service.
The service looks a bit like this:
serviceModule.factory('filtersService', ['$rootScope', 'urlService', function($rootScope, urlService){
return {
getFilters: function(){
if(urlService.getPathName() == "test")
{
return "something";
}
}]);
In the start I just tried to use window.location.pathname instead of creating a service, but it seemed like the wrong way to be able to mock it. I therefore created the urlService, which is basically a simple wrapper around the window-object.
I then have my filtersServiceTest which looks like this:
describe('filtersService test', function(){
var filtersService, urlServiceMock;
beforeEach(module('App.services'));
beforeEach(function(){
urlServiceMock = {
getPathName: function(){
return "";
}
};
module(function($provide){
$provide.value('urlService', urlServiceMock);
});
});
it('Should return something if url is test', inject(function(filtersService){
urlServiceMock = {
getPathName: function(){
return "test";
}
};
expect(filtersService.getFilters()).not.toBe("something");
}));
});
But this doesn't seem to work. I can't overload the urlServiceMock before it is actually run. I could change the 'beforeEach' each so getPathName returns "test", but then I would not be able to test scenarious where the urls isn't equal to test.
You can spy on a service in your test suite to change the behavior:
spyOn(yourService, 'methodName');
There is a great example on jsFiddle on how to implement a spy: http://jsfiddle.net/robinroestenburg/aDwva/
That should allow you get it working correctly.