I am using Jasmine & Karma for unit testing angular app . I have wrote unit test like this
it('should match request object', inject([UserService, MockBackend], (userService: UserService, mockBackend) => {
mockBackend.connections.subscribe((connection: MockConnection) => {
expect(connection.request.method).toEqual(RequestMethod.Post);
expect(connection.request.json().getUserProfileRequest).toEqual({
userid: '1234',
});
connection.mockRespond(new Response(new ResponseOptions({
body: UsersMockData.GET_USER_PROFILE,
})));
});
usersService.getUserProfile(1234)
.subscribe(data => {
expect(data).toBe(UsersMockData.GET_USER_PROFILE);
});
}));
Everything works fine and no issue now when I split test cases in to two separate test cases I write code like this
it('Check userProfile request', inject([UserService, MockBackend], (userService: UserService, mockBackend) => {
mockBackend.connections.subscribe((connection: MockConnection) => {
expect(connection.request.method).toEqual(RequestMethod.Post);
expect(connection.request.json()getUserProfileRequest).toEqual({
userid: '1234',
});
});
}));
it('check return data from service', inject([UserService, MockBackend], (userService: UserService, mockBackend) => {
connection.mockRespond(new Response(new ResponseOptions({
body: UsersMockData.GET_USER_PROFILE,
})));
usersService.getUserProfile(1234)
.subscribe(data => {
expect(data).toBe(UsersMockData.GET_USER_PROFILE);
});
}));
Both of these test cases are having expect statement but when I execute test cases , I see a message SPEC HAS NO EXPECTATIONS for both test cases . I am wondering why it shows spec has no expectations.
If you don't .subscribe to you method, the get request will never be made so the mock backend is never invoked. If you don't provide a mock response, the subscription to your method will never receive a value. Therefore, to reach the expectations at all, you must have a certain amount of minimal wiring in each test. In your case:
it('Check userProfile request', inject([UserService, MockBackend], (userService: UserService, mockBackend) => {
mockBackend.connections.subscribe((connection: MockConnection) => {
expect(connection.request.method).toEqual(RequestMethod.Post);
expect(connection.request.json().getUserProfileRequest).toEqual({
userid: '1234',
});
});
usersService.getUserProfile(1234).subscribe(data => {});
}));
it('check return data from service', inject([UserService, MockBackend], (userService: UserService, mockBackend) => {
mockBackend.connections.subscribe((connection: MockConnection) => {
connection.mockRespond(new Response(new ResponseOptions({
body: UsersMockData.GET_USER_PROFILE,
})));
});
usersService.getUserProfile(1234)
.subscribe(data => {
expect(data).toBe(UsersMockData.GET_USER_PROFILE);
});
}));
Related
I've got a saga that has some error handling logic in it - I want to test that a call is made three times and provide a response for each invocation.
The use case is that the saga retries on the first two errors before giving up, so I need a sequence of response: [fail, fail, success]
it("must succeed after the first two requests are failures", () =>
expectSaga(
sagaToTest
).provide([
[
call(getdata, request),
throwError(new Error("oops")) // do this twice and succeed on the third invication
]
])
.call(getdata, request)
.call(getdata, request)
.call(getdata, request)
.put(actions.itSucceeded("message"))
.run());
});
This is straightforward in other testing / mocking libraries but for some reason I can't seem to find the right documentation.
Thanks!
This library does exactly that https://www.npmjs.com/package/saga-test-stub
You'll need to split your code tho, first encapsulate the throwable call in a separate saga and test it
function* callApi(request: any){
try {
const response = call(getdata, request);
return {sucess:true,response}
}
catch (e){
return {sucess:false}
}
}
describe('callApi saga', () => {
let sagaStub: SagaStub;
beforeEach(() => {
sagaStub = stub(callApi, {});
});
describe('when call to api fails', () => {
beforeEach(() => {
jest.spyOn(api,'callApi').mockImplementation(()=> {
throw new Error()
});
it('should return success false', () => {
expect(saga).toBeDone({sucess:false})
});
});
});
describe('when call to api works', () => {
// ...
});
});
then stub the yielded values from the first saga
describe('sagaToTest', () => {
let sagaStub: SagaStub;
beforeEach(() => {
sagaStub = stub(sagaToTest, {});
when(sagaStub).yields(call(callApi,{})).doNext(
{succes: false},
{succes: false},
{succes: true, response: 'here you go'},
)
});
it('must succeed after the first two requests are failures', () => {
expect(sagaStub).toYield(
call(callApi,{}), //note: this is redundant since it is stubbed
call(callApi,{}), //note: this is redundant since it is stubbed
call(callApi,{}), //note: this is redundant since it is stubbed
put(actions.itSucceeded("message"))
)
});
});
I'm writing unit tests for a React application. A click-handler calls a simple promise, and updates the state inside of '.then()'. I have successfully mocked the promise and am able to enter the correct if/else block using mock data returned from the resolved promise.
It seems that using a console.log to test the data shows that the data is partially incorrect. I'm also unable to test that the state has changed, as (I suppose) setState is asynchronous.
I have tried using .update(), .forceUpdate(), setTimeout(), and setImmediate(). I'm not sure what else to try to be able to test that the state has changed correctly.
Here is the method being tested:
this.handleClick = () => {
sendMessage(this.urlParams.data1, this.urlParams.data2, this.urlParams.data3)
.then((data) => {
if (data.messageType === 'error') {
console.log(data.message);
this.setState({
error: data.message,
}, () => {
console.log(data);
});
} else {
this.doSomething();
}
});
};
Here is the mock of the 'sendMessage' method, which is working as expected:
export const sendPnrgovMessage = () => Promise.resolve({ data: { messageType: 'error', message: 'asdf' } });
Here is the test itself:
it('should send the message when the button is clicked', () => {
renderedInstance = rendered.instance();
renderedInstance.handleClick();
renderedInstance.forceUpdate(() => {
expect(rendered.state('error')).toEqual('asdf');
});
});
I've also tried without the '.forceUpdate()', the result is the same.
There are two issues with the result.
1.) 'data' in the '.then()' evaluates to this:
{ messageType: 'error', message: undefined }
Why would message be undefined, while messageType is correct?
2.) The expected value of this.state.error is 'asdf', or in the above case, 'undefined', but it is actually 'null', which is the initialized value. I assumed that this is because setState is asynchronous and is not being updated by the time the test has finished. I have not found a way around this.
Have you tried mocking the function implementation using jest?
import { sendMessage } from './file';
describe('should test component', () => {
beforeAll(() => {
sendMessage = jest.fn().mockResolvedValue({ data: { message, Type: 'error', message: 'asdf' } });
});
afterAll(() => sendMessage.mockRestore());
it('should send the message when the button is clicked', () => {
renderedInstance = rendered.instance();
renderedInstance.handleClick();
renderedInstance.forceUpdate(() => {
expect(rendered.state('error')).toEqual('asdf');
});
});
});
You made 2 mistakes in your code.
The return value of sendMessage is { data: { messageType: 'error', message: 'asdf' } }
so you should receive the message as {data}. curly brack is required.
When we use the arrow function, we must avoid the 'this' context because it doesn't bind own this.
The answer is as follows:
this.handleClick = () => {
const ref = this;
sendMessage(this.urlParams.data1, this.urlParams.data2, this.urlParams.data3)
.then(({data}) => {
if (data.messageType === 'error') {
console.log(data.message);
ref.setState({
error: data.message,
}, () => {
console.log(data);
});
} else {
ref .doSomething();
}
});
};
Assuming I have a module which returns a promise.
I want to mock different outcomes of this promise to test the function where this module is part of. I mock the module like this:
jest.mock('../request', () => {
return () => new Promise((resolve, reject) => {
return resolve({
response: { ok: true }
});
});
});
My first test is running
test("The function resolves", () => {
const initialState = { apiData: getState("postData", {}, "ready", "POST") };
const store: any = mockStore(initialState);
return expect(
performApiRequest("postData/", {}, { data: "json" })(dispatch, () =>
store.getState()
)
).resolves.toBeUndefined();
});
The problem is now with testing an other function where the value that resolves is supposed to be different, for instance {response: { ok: false } }.
I already tried different things. First wrapping the mock in a function and give the response as an argument. --> fails for mocks can't take out of scope variables.
I tried to call jest.doMock within the test but this does not trigger the request correctly.
I tried to do
const mockResponse = jest.fn();
jest.mock("../request", () => {
return () =>
new Promise((resolve, reject) => {
return resolve({
mockResponse
});
});
});
And then call mockResponse.mockReturnValueOnce(value).
No success yet. Anybody sees a way out?
You can create a default mock function at the top level with jest.fn. Once you create the mock you can override the implementation of the function within the test case with mockImplementation or mockImplementationOnce. You can find more information about this in the Jest documentation.
import request from '../request';
jest.mock("../request", () =>
jest.fn(() =>
Promise.resolve({
response: {
ok: true
}
})
)
);
test("MyTest", () => {
request.mockImplementationOnce(() =>
Promise.resolve({
response: {
ok: false
}
})
);
});
answer with typescript would be:
import request from '../request';
jest.mock("../request", () =>
jest.fn(() =>
Promise.resolve({
response: {
ok: true
}
})
)
);
test("MyTest", () => {
(request as jest.Mock).mockImplementationOnce(() =>
Promise.resolve({
response: {
ok: true
}
})
);
});
I am new to TDD and am testing an authInterceptor (I have chai/mocha/sinon available to me) in angular js, which has two functions, a request, and a responseError. I successfully tested the request function, but I don't know how (scoured the docs) to mock a 401 (unauthorized) error. Here is the interceptor:
export default function AuthInterceptor($q, $injector, $log) {
'ngInject';
return {
request(config) {
let AuthService = $injector.get('AuthService');
if (!config.bypassAuthorizationHeader) {
if (AuthService.jwtToken) {
config.headers.Authorization = `Bearer ${AuthService.jwtToken}`;
} else {
$log.warn('Missing JWT', config);
}
}
return config || $q.when(config);
},
responseError(rejection) {
let AuthService = $injector.get('AuthService');
if (rejection.status === 401) {
AuthService.backToAuth();
}
return $q.reject(rejection);
}
};
}
Here are my four tests. the first three 'it' blocks pass successfully, the fourth is where I am stuck, I have added comments in that "it" block:
import angular from 'angular';
import AuthInterceptor from './auth.interceptor'
describe('Auth interceptor test', () => {
describe('AuthInterceptor test', () => {
let $httpBackend, $http, authInterceptor = AuthInterceptor();
beforeEach(angular.mock.module(($httpProvider, $provide) => {
$httpProvider.interceptors.push(AuthInterceptor);
$provide.factory('AuthService', () => ({
jwtToken: "hello",
backtoAuth: angular.noop
}));
}));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$http = $injector.get('$http');
}))
it('should have a request function', () => {
let config = {};
expect(authInterceptor.request).to.be.defined;
expect(authInterceptor.request).to.be.a('function');
})
it('the request function should set authorization headers', (done) => {
$httpBackend.when('GET', 'http://jsonplaceholder.typicode.com/todos')
.respond([{
id: 1,
title: 'Fake title',
userId: 1
}]);
$http.get('http://jsonplaceholder.typicode.com/todos').then(function(transformedResult) {
expect(transformedResult.config.headers.Authorization).to.be.defined;
expect(transformedResult.config.headers.Authorization).to.contain('Bearer')
done();
})
$httpBackend.flush();
});
it('should have a responseError function', () => {
expect(authInterceptor.responseError).to.be.defined;
expect(authInterceptor.responseError).to.be.a('function');
//TODO: test return value
// see that AuthService.backToAuth()
})
it('the error function should call backtoAuth', (done) => {
//a url that doesn't give me a 401 like I'm hoping.
$httpBackend.when('POST', 'https://wwws.mint.com/overview.event').respond([
//what do I do here?
])
$http.post('https://wwws.mint.com/overview.event').then(function(transformedResult) {
console.log("success", transformedResult);
done();
}, function(error){
// I can't seem to get in here. if I can, the responseError should be called, which in turn calls backToAuth...
console.log("error", error);
done();
})
$httpBackend.flush();
});
The first respond parameter is status, it should be
$httpBackend.when('POST', 'https://wwws.mint.com/overview.event').respond(401);
It is always good to use Sinon/Jasmine spies/stubs instead of noops in stubbed methods, so their calls could be tested:
var sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => {
sandbox.restore();
});
beforeEach(angular.mock.module(($httpProvider, $provide) => {
$httpProvider.interceptors.push(AuthInterceptor);
$provide.factory('AuthService', () => ({
jwtToken: "hello",
backtoAuth: sandbox.stub();
}));
}));
Parent Service:
module proj.Stuff {
export class ParentService {
//...properties, constructor, etc
public refreshStuff(id: number) {
this.childService
.getStuff(id)
.then((response) => this.stuff = response);
}
}
}
Child service:
module proj.Stuff {
export class ChildService{
//... properties, constructor, etc
public getStuff(id: number) {
var request: IPromise<any> = this.$http.get(
ChildService.apiUrlBase + "getStuff/" + id
);
return request
.then(response => {
return response.data.value;
}, response => {
this.$log.error("unable to get...");
});
}
}
}
Tests for the parent service:
describe("ParentService", () => {
// (property declarations omitted for brevity)
beforeEach(angular.mock.module(["$provide", ($provide) => {
var obj = {
getStuff: (id: number) => {
functionCalled = true;
return {
then: (callback) => {
return callback(["result"]);
}
};
}
};
$provide.value("ChildService", obj);
}]));
beforeEach(mock.inject((_$http_, _$log_, _$q_, _$httpBackend_, _$rootScope_, _ChildService_) => {
cService = _ChildService_;
pService = new ParentService(cbService);
}));
it("can be created", () => {
expect(pService).toBeDefined();
expect(pService).not.toBeNull();
});
it("can refresh stuff", () => {
pService.refreshStuff(1);
expect(pService.stuff).toEqual(["result"]);
expect(functionCalled).toBeTruthy();
// ***** what I want to do: *****
// expect(cService.getStuff).toHaveBeenCalled();
});
});
I'm wondering how can I spy on cService.getStuff instead of using the 'functionCalled' boolean?
When I try to spy on it, it complains that .then isn't defined - e.g. in the first beforeEach if I try spyOn(obj, "getStuff") it doesn't like it.
The tests pass as is, but would rather spyOn instead of using the boolean.
then method mocks are rarely justified, Angular DI allows to use unmocked promises and to focus on unit testing.
beforeEach(angular.mock.module(["$provide", ($provide) => {
// allows to inject $q, while $provide.value doesn't
$provide.factory("ChildService", ($q) => ({
// fresh promise on every call
getStuff: jasmine.createSpy('getStuff').and.callFake(() => $q.when('result'))
}));
}]));
Works best with Jasmine promise matchers, otherwise routine promise specs should be involved:
var result;
...then((_result) => { result = _result; })
$rootScope.$digest();
expect(result)...