is there a way to stub a function using jest API?
I'm used to working with sinon stub, where I can write unit-tests with stubs for any function call coming out of my tested unit-
http://sinonjs.org/releases/v1.17.7/stubs/
for example-
sinon.stub(jQuery, "ajax").yieldsTo("success", [1, 2, 3]);
With jest you should use jest.spyOn:
jest
.spyOn(jQuery, "ajax")
.mockImplementation(({ success }) => success([ 1, 2, 3 ]));
Full example:
const spy = jest.fn();
const payload = [1, 2, 3];
jest
.spyOn(jQuery, "ajax")
.mockImplementation(({ success }) => success(payload));
jQuery.ajax({
url: "https://example.api",
success: data => spy(data)
});
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(payload);
You can try live example on codesandbox: https://codesandbox.io/s/018x609krw?expanddevtools=1&module=%2Findex.test.js&view=editor
Jest provides jest.fn(), which has some basic mocking and stubbing functionality.
If you're experienced and comfortable with sinon you could still create Jest-based tests which use sinon test doubles. However you'll lose the convenience of built in Jest matchers such as expect(myStubFunction).toHaveBeenCalled().
Doing following two thing, got it working for me.
Adding __esModule:true fixed this issue for me.
jest.mock('module',()=>({ __esModule: true, default: jest.fn() }));
Moving the mocking part before the describe. (Just after the imports.)
//moving it to before the describe ->
jest.mock(...); describe('', ...);
Hope this helps somebody.
I was able to sub out jquery entirely by using mockReturnValue and jquery's $.Deferred. This allowed me to manually resolve my ajax calls and then the rest of the function would continue (and any chaining of .done() or .success() etc would execute.
Example:
const deferred = new $.Deferred();
$.ajax = jest.fn().mockReturnValue(deferred);
myClass.executeAjaxFunction();
const return_val = 7;
deferred.resolve(return_val)
Then if I have a function like
$.ajax({
type: 'GET',
url: '/myurl'
}).done((val) => {
window.property = val;
});
The following test will pass
it('should set my property correctly', () => {
expect(window.property).toBe(7);
});
Of course- you can skip the deferred part of this answer if you are trying to stub a non-jquery function. I came across this question that dealt with ajax and came up with this solution as a way to test a function that executes actions after an ajax call is complete using Jest.
Related
I'm trying to create units test for my stencil js component, in the compnentWillLoad() method it will do an HTTP request (using rxjs).when I'm run the test getting error ReferenceError: XMLHttpRequest is not defined.But when removing the HTTP request from the componentWillLoad() method test passed.
My test as below,
it('should render my component', async () => {
const page = await newSpecPage({
components: [MyComponent],
html: `<my-component></my-component>`,
});
expect(page.root).toEqualHtml(`<my-component></my-component>`);
});
I'm getting error ReferenceError: XMLHttpRequest is not defined
XMLHttpRequest is indeed not defined in the virtual DOM context that is created when you use newSpecPage.
The best solution for you is probably to write this as an E2E test instead, using newE2EPage, which is more suited for complete end-to-end testing because it runs in a real browser context where XMLHttpRequest will be available.
it('should render', async () => {
const page = await newE2EPage({ html: '<my-component></my-component>' });
const myComponent = page.find('my-component');
expect(myComponent).toHaveClass('hydrated');
});
"Spec Page" testing is rather meant for unit testing components that work stand-alone. If your goal is to actually unit-test your component and you just want to be able to instantiate your component but you don't actually need the request to succeed for testing, then you can also use the Build context from Stencil:
import { Build, ... } from '#stencil/core';
export class MyComponent {
componentWillLoad() {
if (!Build.isTesting) {
// make the request
}
}
// ...
}
I had similar troubles with Stencil, Jest and XMLHttpRequest.
First, make sure you call
new window.XMLHttpRequest()
instead of simply calling
new XMLHttpRequest()
This seems to be neccessary when using jsdom and may already resolve your issue.
It didn't resolve mine though, since I wanted to make sure there are no real API calls going on. So I tried to mock XMLHttpRequest. However, I ran into other issues while building the mock and finally decided to refactor my code to use Fetch API instead of XMLHttpRequest which seems to be better supported by Stencil.
You can easily mock fetch using jest
export function mockFetch(status, body, statusText?) {
// #ts-ignore
global.fetch = jest.fn(() =>
Promise.resolve({
status: status,
statusText: statusText,
text: () => Promise.resolve(JSON.stringify(body)),
json: () => Promise.resolve(body),
})
)
}
So I am trying to understand how to properly unit test my functions that call firebase in them. I want to stub or spy on these firebase functions to just see they are called but not having the actual firebase call go out since that would be more of an integration test by my understanding. I don't quite know the right way to go about this.
This is a VueJs application using Jest with Sinon for testing.
This is a function from one of my Vue components.
-Login.Vue-
login() {
this.performingRequest = true // initialized as false
firebase.auth
.signInWithEmailAndPassword(
this.loginForm.email,
this.loginForm.password
)
.then(user => {
this.$store.commit("setCurrentUser", user.user);
this.$store.dispatch("fetchUserProfile");
this.performingRequest = false
this.$router.push("/mainPage");
})
.catch(err => {
console.log(err);
this.performingRequest = false
this.errorMsg = err.message
});
},
My test
describe('Login.vue', () => {
const wrapper = shallowMount(Login);
test('Login function works as expected', () => {
expect(wrapper.vm.performingRequest).toBe(false); // passes
let spy = sinon.spy(wrapper.vm.login());
spy.call();
expect(wrapper.vm.performingRequest).toBe(true); // passes
expect(spy.calledOnce).toBe(true);
// expect(wrapper.vm.performingRequest).toBe(false); // passes
})
});
What I want to do is see that the firebase auth function got called and that the values it effects actually got changed.
Trying to understand what to stub / spy on has been difficult. Any help in understanding this process would be much appreciated because I have a lot more of this application to test :D
Thank you for your help in advanced and let me know if I need to provide more information for clarities sake.
In the meantime I will be researching :)
EDIT:
Seems it was just user error on my part. Hope this can help someone!
This did the trick -
const stub = sinon.stub(fb.auth, 'signInWithEmailAndPassword').resolves('returning promise');
My Controller function definition looks like that:
async login(#Req() request, #Body() loginDto: LoginDto): Promise<any> {
How I could prepare/mockup Request to provide first argument of function from Jest test?
Inside funciton I am setting headers using request.res.set. Should I somehow pass real Request object to function and then check if header is set or rather mockup whole Request object and check if set function was called?
I managed to do that mocking requests and response using node-mocks-http library.
const req = mocks.createRequest()
req.res = mocks.createResponse()
and then passing this as an argument.
const data = await authController.login(req, loginDto)
expect(req.res.get('AccessToken')).toBe(token.accessToken)
I followed a different approach and instead of using node-mocks-http library I used #golevelup/ts-jest, also, instead of testing if the function returns some value, like res.json() or res.status(), I checked if the function was called with the value I wanted to.
I borrowed this approach from Kent C. Dodds's testing workshop, take a look for similar ideas. Anyway, this is what I did in order to mock the Response dependency of my Controller's route:
// cars.controller.spec.ts
import { createMock } from '#golevelup/ts-jest';
const mockResponseObject = () => {
return createMock<Response>({
json: jest.fn().mockReturnThis(),
status: jest.fn().mockReturnThis(),
});
};
... ommited for brevity
it('should return an array of Cars', async () => {
const response = mockResponseObject();
jest
.spyOn(carsService, 'findAll')
.mockImplementation(jest.fn().mockResolvedValueOnce(mockedCarsList));
await carsController.getCars(response);
expect(response.json).toHaveBeenCalledTimes(1);
expect(response.json).toHaveBeenCalledWith({ cars: mockedCarsList });
expect(response.status).toHaveBeenCalledTimes(1);
expect(response.status).toHaveBeenCalledWith(200);
});
And that's it, I think that the implementation details aren't that important but in any case I'll leave the link to the Github repo where you can find the whole project.
In React Native I use fetch to perform network requests, however fetch is not an explicitly required module, so it is seemingly impossible to mock in Jest.
Even trying to call a method which uses fetch in a test will result in:
ReferenceError: fetch is not defined
Is there a way to test such API requests in react native with Jest?
Inside your test case you can mock any function you want by using Jest's mocks:
fetch = jest.fn(() => Promise.resolve());
This approach works only for the promise-based test cases (see pit in the Jest docs).
As far as fetch is an async function, you need to run all your tests using pit (read more about async tests here).
Another approach where you mock the global fetch object:
const mockSuccesfulResponse = (
status = 200,
method = RequestType.GET,
returnBody?: object
) => {
global.fetch = jest.fn().mockImplementationOnce(() => {
return new Promise((resolve, reject) => {
resolve({
ok: true,
status,
json: () => {
return returnBody ? returnBody : {};
},
});
});
});
};
The above helper method can be modified any way you want :-) Hope it helps someone
Rather than rolling your own mock, you can use the jest-fetch-mock npm package to override the global fetch object. That package allows you to set up fake responses and verify sent requests. See that link for extensive usage examples.
I solved this by adding isomorphic-fetch.
$ npm install --save isomorphic-fetch
and using it like
import fetch from 'isomorphic-fetch';
...
fetch('http://foo.com');
whatwg-fetch might work as well
Suppose you want to test resolve and reject cases, for this first you mock the fetch behaviour and then use Jest's rejects and resolves methods with with assertion block
function fetchTodos() {
return fetch(`${window.location.origin}/todos.json`)
.then(response => response.json())
.catch(error => console.log(error))
}
describe('fetchTodos', () => {
it('returns promise resolving to parsed response', () => {
global.fetch = jest.fn(() => Promise.resolve({ json: () => ''}))
expect(fetchTodos()).resolves.toBe('');
})
it('returns promise handling the error', async () => {
global.fetch = jest.fn(() => Promise.reject(''))
expect(fetchTodos()).rejects.toBe('')
})
})
As #ArthurDenture recommended, you can use fetch-mock, but there are some additional packages you will need to install to make it work with React Native and Jest:
$ npm install --save-dev fetch-mock
$ npm install --save-dev babel-plugin-transform-runtime
$ npm install --save-dev babel-preset-env
You can then mock fetch requests in your tests. Here is an example:
// __tests__/App.test.js
import React from 'react';
import App from '../App';
import fetchMock from 'fetch-mock';
import renderer from 'react-test-renderer';
it('renders without crashing', () => {
fetchMock.mock('*', 'Hello World!');
const rendered = renderer.create(<App />).toJSON();
expect(rendered).toBeTruthy();
});
Due to problems using fetch-mock with jest, I've release fetch-mock-jest. It basically gives the full fetch-mock api, but with a few jest-specific helpers, and works out of the box with jest, without needing to do any tricky wiring yourself
As shown in the react-testing-library documentation, you can use the jest.spyOn() function, which will mock the fetch function only for the next time it is called.
const fakeUserResponse = {token: 'fake_user_token'}
jest.spyOn(window, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
json: () => Promise.resolve(fakeUserResponse),
})
})
react-testing-library
As you know, inside unit tests it's built-in angularjs feature to mock XHR requests with $httpBackend - this is nice and helpful while writing unit tests.
Recently, I met with need of mocking XHR in case of file upload and discovered some problems.
Consider following code:
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", uploadProgress(event), false);
xhr.addEventListener("load", uploadComplete(event), false);
xhr.addEventListener("error", uploadError(event), false);
xhr.addEventListener("abort", uploadAbort(event), false);
xhr.open("POST", 'some url');
xhr.send(someData);
What I want to do is to do unit testing of such a code with mocking of XHR requests, but it's not possible do it because there is no $http service used here.
I tried this (and it's working and could be mocked with $httpBackend):
$http({
method: 'POST',
url: 'some url',
data: someData,
headers: {'Content-Type': undefined},
transformRequest: angular.identity})
.then(successCallback, errorCallback);
But in this case I don't know how to implement 'progress' callback and 'abort' callback (they are essential and required in case I am working on now).
I've seen information that latest Angular supports progress callback for promises (not sure though whether it's integrated with $http service), but what about abort callback?
Any ideas or maybe your met with something similar before?
If the $http service doesn't give you everything you need, you can still unit test the first block of code. First of all, change your code to use Angular's $window service. This is just a wrapper service, but it allows you to mock the object in your tests. So, you'll want to do this:
var xhr = new $window.XMLHttpRequest();
Then in your tests, just mock it and use spies.
$window.XMLHttpRequest= angular.noop;
addEventListenerSpy = jasmine.createSpy("addEventListener");
openSpy = jasmine.createSpy("open");
sendSpy = jasmine.createSpy("send");
xhrObj = {
upload:
{
addEventListener: addEventListenerSpy
},
addEventListener: addEventListenerSpy,
open: openSpy,
send: sendSpy
};
spyOn($window, "XMLHttpRequest").andReturn(xhrObj);
From there, you can make the different spies return whatever you want for the different tests.
You should mock $http and control any deferred, as you want more control over your test. Basically, mock $http provider and serve a custom implementation that exposes its deferred, then play with it.
You should not worry whether $http is working right or not, because it is supposed to, and is already tested. So you have to mock it and only worry testing your part of the code.
You should go something like this:
describe('Testing a Hello World controller', function() {
beforeEach(module(function($provide) {
$provide.provider('$http', function() {
this.$get = function($q) {
return function() {
var deferred = $q.defer(),
promise = deferred.promise;
promise.$$deferred = deferred;
return promise;
}
};
});
}));
it('should answer to fail callback', inject(function(yourService, $rootScope) {
var spyOk = jasmine.createSpy('okListener'),
spyAbort = jasmine.createSpy('abortListener'),
spyProgress = jasmine.createSpy('progressListener');
var promise = yourService.upload('a-file');
promise.then(spyOk, spyAbort, spyProgress);
promise.$$deferred.reject('something went wrong');
$rootScope.$apply();
expect(spyAbort).toHaveBeenCalledWith('something went wrong');
}));
});
And your service is simply:
app.service('yourService', function($http) {
return {
upload: function(file) {
// do something and
return $http({...});
}
};
});
Just note that promises notification is only available in the latest RC release. So, if you can't use it, just elaborate a little more the example and mock the XHR events and so.
Also note that you should preferably have one test case for each of the callbacks (fail, success and progress), in order to follow KISS principle.