I have written a small interceptor using axios that would flush out localBrowserStorage and would redirect user to login page if response code is 401.
It works fine but I am getting some errors in the unit test.
Test
describe('Api Service', () => {
let sandbox;
beforeEach(() => {
moxios.install();
sandbox = sinon.sandbox.create();
});
afterEach(() => {
moxios.uninstall();
sandbox.restore();
});
describe.only('interceptors', () => {
it('clear storage and redirect to login if response status code is 401', (done) => {
moxios.withMock(() => {
sandbox.spy(browserHistory, 'push');
sandbox.spy(storage, 'clear');
axios.get('/test');
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 401,
response: {}
}).then(() => {
expect(browserHistory.push).to.have.been.calledWith('/login');
expect(storage.clear).to.have.been.called; // eslint-disable-line no-unused-expressions
done();
});
});
});
});
});
});
I get these two warnings with this:
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: Request failed with status code 401
(node:5338) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): AssertionError: expected push to have been called with arguments /login
And this error:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
EDIT:
axios.interceptors.response.use((response) => {
if (response.status === 401) {
storage.clear();
browserHistory.push('/login');
return response;
}
return response;
});
You need to pass rejections back to Mocha by calling done in the catch handler:
request.respondWith({
status: 401,
response: {}
}).then(() => {
expect(browserHistory.push).to.have.been.calledWith('/login');
expect(storage.clear).to.have.been.called; // eslint-disable-line no-unused-expressions
done();
}).catch(done);
If you don't handle rejected promises, there is a chance that done never gets called (resulting in a timeout).
For instance, if one of the expectations fails, it will throw an error. When that happens, the call to done following the expectation lines will never get executed. You can (and should) catch the error with a .catch clause, as above. That should call done with the error. The code I'm using is short for:
.catch(function(err) {
done(err);
})
Related
I have written test cases for signin API using jest. After completing all five test of a test suit jest give me following error in log.
Can any body tell Why it is So and how to fix it?
CODE:(signup.test.ts)
import request from 'supertest';
import { TYPES } from '../src/inversify.types'
import { Application } from '../src/app/Application'
import { container } from '../src/inversify.config'
import dotenv from 'dotenv'
import { RESPONSE_CODE } from '../src/utils/enums/ResponseCode'
import { RESPONSE_MESSAGES } from '../src/utils/enums/ResponseMessages'
import { UserSchema } from '../src/components/user/User';
// import jwt from 'jsonwebtoken';
var application: Application
describe("POST / - SIGNUP endpoint", () => {
// var testusers: any;
//This hook is executed before running all test cases, It will make application instance, make it to listen
// on it on port 3000 and add test document in DB
beforeAll(async () => {
// Make enviroment variables available throughout the application
dotenv.config();
// Getting application instance using iversify container
application = container.get<Application>(TYPES.Application);
// Initialize frontside of application
await application.bootstrap();
// Starting Application server on given port
await application.listen(3000);
});
afterAll(
//This hook is executed after running all test cases and delete test document in database
async () =>{
const res = await UserSchema.deleteMany({ Name: { $in: [ "Test User", "Test" ] } });
// `0` if no docs matched the filter, number of docs deleted otherwise
console.log('---------------------->>>>>>>>>>>>>>>>>>>', (res as any).deletedCount);
}
)
it("Signup for user that don\'t exists", async () => {
const response = await request(application.getServer()).post('/user/signup')
.send({
"Email": JSON.parse(process.env.TEST_USER).Email,
"Name": "Test User",
"Password": process.env.TEST_ACCOUNTS_PASSWORD
})
expect(response.status).toBe(RESPONSE_CODE.CREATED);
expect(JSON.parse(response.text)).toEqual(expect.objectContaining({
Message: RESPONSE_MESSAGES.ADDED_SUCESSFULLY,
Data: expect.objectContaining({
Name: 'Test User',
Country: '',
PhoneNumber: '',
// Password: '$2b$10$nIHLW/SA73XLHoIcND27iuODFAArOvpch6FL/eikKT78qbShAl6ry',
Dob: '',
Role: 'MEMBER',
IsEmailVerified: false,
IsBlocked: 'ACTIVE',
IsTokenSent: false,
twoFAStatus: false,
// _id: '5c812e2715e0711b98260fee',
Email: JSON.parse(process.env.TEST_USER).Email
})
})
);
console.log('*** Signup for user that don\'t exists *** response', response.text, 'response status', response.status);
});
it("Signup for user that exists", async () => {
const response = await request(application.getServer()).post('/user/signup')
.send({
"Email": JSON.parse(process.env.TEST_USER).Email,
"Name": "Test User",
"Password": process.env.TEST_ACCOUNTS_PASSWORD
})
expect(response.status).toBe(RESPONSE_CODE.CONFLICT);
expect(JSON.parse(response.text)).toEqual({
Message: RESPONSE_MESSAGES.ALREADY_EXISTS
})
console.log('*** Signup for user that don\'t exists *** response', response.text, 'response status', response.status);
});
});
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't
stopped in your tests. Consider running Jest with
--detectOpenHandles to troubleshoot this issue.
Cannot log after tests are done. Did you forget to wait for something
async in your test?
Attempted to log "{ accepted: [ 'unverifiedtestuser#abc.com' ],
rejected: [],
envelopeTime: 621,
messageTime: 867,
messageSize: 906,
response: '250 2.0.0 OK 1551945300 f6sm5442066wrt.87 - gsmtp',
envelope:
{ from: 'abc#gmail.com',
to: [ 'unverifiedtestuser#abc.com' ] },
messageId: '<45468449-b5c8-0d86-9404-d55bb5f4g6a3#gmail.com>' }".
at CustomConsole.log (node_modules/jest-util/build/CustomConsole.js:156:10)
at src/email/MailHandler.ts:2599:17
at transporter.send.args (node_modules/nodemailer/lib/mailer/index.js:226:21)
at connection.send (node_modules/nodemailer/lib/smtp-transport/index.js:247:32)
at callback (node_modules/nodemailer/lib/smtp-connection/index.js:435:13)
at stream._createSendStream (node_modules/nodemailer/lib/smtp-connection/index.js:458:24)
at SMTPConnection._actionSMTPStream (node_modules/nodemailer/lib/smtp-connection/index.js:1481:20)
at SMTPConnection._responseActions.push.str (node_modules/nodemailer/lib/smtp-connection/index.js:968:22)
at SMTPConnection._processResponse (node_modules/nodemailer/lib/smtp-connection/index.js:764:20)
at SMTPConnection._onData (node_modules/nodemailer/lib/smtp-connection/index.js:570:14)
I was using the react-native default test case (see below) when Cannot log after tests are done happened.
it('renders correctly', () => {
renderer.create(<App />);
});
Apparently, the problem was that the test ended but logging was still needed. So I tried to make the callback in the test case async, hoping that the test won't terminate immediately:
it('renders correctly', async () => {
renderer.create(<App />);
});
And it worked. However, I have very little clue what the inner working is.
If you are using async/await type in your code, then this error can occur when you are calling async function without await keyword.
In my case, I have defined a function like this below,
async getStatistics(headers) {
....
....
return response;
}
But I have called this method like getStatistics(headers) instead of await getStatistics(headers).
When I included await, it worked fine and the issue resolved.
In my case while using nodejs + jest + supertest the problem was that when I import app from "./app" to my test file to do some stuff with supertest (request(app)), I actually import with app.listen() , because when I'm exporting app, export takes in account app.listen() too, but we don't need app.listen() in tests and it throws an error
"Cannot log after tests are done.Did you forget to wait for something async in your test?"
Here is all in one file(that's the problem!)
const app = express();
app.use(express.json());
// ROUTES
app.get("/api", (req, res) => {
res.json({ message: "Welcome to Blog API!" });
});
app.use("/api/users", usersRoutes);
app.use("/api/blogs", blogsRouter);
// The server will start only if the connection to database is established
mongoose
.connect(process.env.MONGO_URI!)
.then(() => {
console.log("MongoDB est connecté");
const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`The server is running on port: ${port}`));
})
.catch(err => {
console.log(err);
});
export default app;
To solve this issue I created 2 separate folders:
// 1) app.ts
Where I put all stuff for my const app = express(), routes etc and export app
dotenv.config();
const app = express();
app.use(express.json());
// ROUTES
app.get("/api", (req, res) => {
res.json({ message: "Welcome to Blog API!" });
});
app.use("/api/users", usersRoutes);
app.use("/api/blogs", blogsRouter);
export default app;
// 2) index.ts
Where I put app.listen() and mongoose.connection() and import app
*import mongoose from "mongoose";
import app from "./app";
// The server will start only if the connection to database is established
mongoose
.connect(process.env.MONGO_URI!)
.then(() => {
console.log("MongoDB est connecté");
const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`The server is running on port: ${port}`));
})
.catch(err => {
console.log(err);
});*
For me I needed to add an await before the expect() call also to stop this error (and an async before the test() callback function).
Also caused and fixed Jest not detecting coverage on the lines in the code throwing the error!
test("expect error to be thrown for incorrect request", async () => {
await expect(
// ^ added this
async () => await getData("i-made-this-up")
).rejects.toThrow(
"[API] Not recognised: i-made-this-up"
);
});
getData() returns an Axios call and in this case an error is caught by catch and re-thrown.
const getData = async (id) => {
return await axios
.get(`https://api.com/some/path?id=${id}`)
.then((response) => response.data)
.catch((error) => {
if (error?.response?.data?.message) {
console.error(error) // Triggered the error
throw new Error("[API] " + error.response.data.message);
}
throw error;
});
};
This happened to me because I had an infinite loop while (true). In my case, I was able to add a method for setting the value of the loop based on user input, rather than defaulting to true.
In my case, the error was caused by asynchronous Redis connection still online. Just added afterall method to quit Redis and could see the log again.
Working on Typescript 4.4.2:
test("My Test", done => {
let redisUtil: RedisUtil = new RedisUtil();
let redisClient: Redis = redisUtil.redis_client();
done();
});
afterAll(() => {
redisClient.quit();
});
I solved it with the env variables:
if (process.env.NODE_ENV !== 'test') {
db.init().then(() => {
app.listen(PORT, () => {
console.log('API lista por el puerto ', PORT)
})
}).catch((err) => {
console.error(err)
process.exit(1)
})
} else {
module.export = app
}
I faced same warnings. However the fix is bit weird:
The jest unit test script import a function (which is not export from src/). After I added the export to the function to be tested. The error disappears.
I had a similar issue:
Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "Warning: You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. ".
It was due to a missing static keyword. This code caused the issue:
class MyComponent extends React.Component<Props, State> {
propTypes = {
onDestroy: PropTypes.func,
}
}
It should have been:
class MyComponent extends React.Component<Props, State> {
static propTypes = {
onDestroy: PropTypes.func,
}
}
I'm new to Angular testing and am trying to figure out how to write a test that mocks an error response of HttpClient.get() function. Basically my service has both map() and catchError() inside of its pipe() and I would like to excercise both flows. Here's what I have so far:
my.service.ts:
getItems(): Observable<ItemViewModels[]> {
return
this.httpClient.get<any>(this.getItemsUrl)
.pipe(
map(json => {
return json.map(itemJson => this.getVmFromItemJson(itemJson));
}),
catchError(() => {
// Log stuff here...
return of(null);
})
);
}
my.service.spec.ts:
it('should catch error and return null if API returns error', () => {
spyOn(service.httpClient, 'get').and.returnValue(new Observable()); // Mock error here
service.getItems().subscribe($items => expect($items).toBe(null));
});
it('should return valid view model array if API returns a valid json', () => {
const mockResponse = [
new SideNavItemViewModel(1),
new SideNavItemViewModel(2),
new SideNavItemViewModel(3)
];
spyOn(service.httpClient, 'get').and.returnValue(of(JSON.stringify(mockResponse)));
service.getSidenavViewModel().subscribe(x => expect(x).toBe(mockResponse));
});
So the actual issue is that the observables that I mock for the httpClient to return on get in the unit tests don't seem to get into the .pipe() function, which means that my tests aren't working :(
Any ideas?
Thanks!
Have you tried injecting your service into the test? I also try test the function that subscribes to the api call instead of creating another subscribe:
for errors:
it('should display error when server error occurs',
inject([HttpTestingController, AService],
(httpMock: HttpTestingController, svc: MyService) => {
svc.getItems(); // has the subscribe in it
const callingURL = svc['API']; // your api call eg. data/items
httpMock.expectOne((req: HttpRequest < any > ) => req.url.indexOf(callingURL) !== -1)
.error(new ErrorEvent('Customer Error', {
error: 500
}), {
status: 500,
statusText: 'Internal Server Error'
});
httpMock.verify();
expect(component.svc.Jobs).toBeUndefined();
fixture.detectChanges();
// UI check here
}));
data test
it('should display the correct amount of data elements',
inject([HttpTestingController, AService],
(httpMock: HttpTestingController, svc: MyService) => {
svc.getItems(); // has the subscribe in it
const callingURL = svc['API']; // your api call eg. data/items
const mockReq = httpMock.expectOne((req: HttpRequest < any > ) => req.url.indexOf(callingURL) !== -1);
mockReq.flush(mockData);
httpMock.verify();
expect(component.svc.data.length).toBe(mockData.length);
fixture.detectChanges();
// UI check here
}));
So basically these functions:
call your get and subscribe
checks that your api url is contained in the http call
mocks the response - you pass in the data - 'mockData'
mockReq.flush(mockData); will trigger the call
httpMock.verify(); will check the url and other things
now the service data can be tested - if you subscribe sets anything in there
fixture.detectChanges(); - then this will allow to test ui components
I prefer this way because you can keep your logic and tests separate.
I have following test cases, both are passing when I expected the test to be marked as failing:
// testing.test.js
describe('test sandbox', () => {
it('asynchronous test failure', done => {
Promise.resolve()
.then(_ => {
// failure
expect(false).toBeTruthy();// test failed
done();// never get called
})
.catch(err => {// this catches the expectation failure and finishes the test
done(err);
});
});
it('asynchronous test success', done => {
Promise.resolve()
.then(_ => {
// failure
expect(true).toBeTruthy();
done();
})
.catch(err => {
console.log(err);
done(err);
});
});
});
Now the output looks as follows:
PASS src\tests\testing.test.js
test sandbox
√ asynchronous test failure (3ms)
√ asynchronous test success (1ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.884s
Ran all test suites matching /testing.test/.
console.log src\tests\testing.test.js:10
{ Error: expect(received).toBeTruthy()
Expected value to be truthy, instead received
false
at Promise.resolve.then._ (C:\dev\reactjs\OmniUI\src\tests\testing.test.js:6:21)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7) matcherResult: { message: [Function], pass: false } }
This is how it started, when I've encountered this for the first time, and it stopped testing any other tests with error,
// sandbox test
describe('test sandbox', () => {
it('asynchronous test failure', done => {
Promise.resolve()
.then(_ => {
// failure
expect(false).toBeTruthy();
done();// expected this to be called, but no
})
});
it('asynchronous test success', done => {
Promise.resolve()
.then(_ => {
// failure
expect(true).toBeTruthy();
done();
})
});
});
will stop the test suite which is not good as well as I want a full test suite done with a results of x failed:
RUNS src/tests/testing.test.js
C:\dev\reactjs\OmniUI\node_modules\react-scripts\scripts\test.js:20
throw err;
^
Error: expect(received).toBeTruthy()
Expected value to be truthy, instead received
false
at Promise.resolve.then._ (C:\dev\reactjs\OmniUI\src\tests\testing.test.js:6:21)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
and in Jest console log:
{
"resource": "/C:/dev/.../src/tests/testing.test.js",
"owner": "Jest",
"code": "undefined",
"severity": 8,
"message": "Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.",
"source": "Jest",
"startLineNumber": 9,
"startColumn": 1,
"endLineNumber": 9,
"endColumn": 6
}
and here is
// real life example
// actions.js with axios call
export const startTransactionStarted = () => ({
type: 'START_CART_TRANSACTION_STARTED',
});
export const startTransactionFinished = (payload, success) => ({
type: 'START_CART_TRANSACTION_FINISHED',
payload,
success,
});
export const startTransaction = () => (dispatch, getState) => {
const state = getState();
const {
transaction: { BasketId = undefined },
} = state;
if (BasketId !== undefined && BasketId !== null) {
// we already have BasketId carry on without dispatching transaction actions
return Promise.resolve();
}
dispatch(startTransactionStarted());
return Axios.post("https://test:181/api/Basket/StartTransaction", {})
.then(response => {
const {
data: { BusinessErrors, MessageHeader },
} = response;
if (BusinessErrors !== null) {
dispatch(startTransactionFinished(BusinessErrors, false));
return Promise.reject(BusinessErrors);
}
dispatch(startTransactionFinished(MessageHeader, true));
return Promise.resolve(response.data);
})
.catch(error => {
// console.log("Axios.post().catch()", error);
dispatch(startTransactionFinished([error], false));
/* eslint-disable prefer-promise-reject-errors */
return Promise.reject([error]);
});
};
// actions.test.js
import Axios from 'axios';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import moxios from 'moxios';
import {
startTransactionStarted,
startTransaction,
startTransactionFinished
} from 'actions';
const createMockStore = configureMockStore([thunk]);
describe('actions.js', () => {
it('test 404 response', done => {
// create mocked store with least amount of data (only those that are needed by tested code)
const store = createMockStore({
transaction: {
BasketId: null,
},
});
// jest mocked function - we are using it to test that dispatch action worked
const onFulfilled = jest.fn();
const onErrorCalled = jest.fn();
const error = { message: '404 - Error' };
const expectedResponse = [error];
// single test with mock adapter (for axios)
moxios.withMock(() => {
// dispatch action with axios call, pass mocked response handler
store
.dispatch(startTransaction())
.then(onFulfilled)
.catch(onErrorCalled);
// wait for request
moxios.wait(() => {
// pick most recent request which then we will "mock"
const request = moxios.requests.mostRecent();
console.log('moxios.wait()');
request
.respondWith({
status: 404,
error,
})
.then(() => {
console.log('- then()');
expect(onFulfilled).not.toHaveBeenCalled();
expect(onErrorCalled).toHaveBeenCalled();
const actions = store.getActions();
console.log(actions);
expect(actions).toEqual([
startTransactionStarted(),
startTransactionFinished(expectedResponse, false),
]);// this fails
done();// this is never called
})
});
});
});
});
test stops with this error
RUNS src/tests/testing.test.js
C:\dev\reactjs\OmniUI\node_modules\react-scripts\scripts\test.js:20
throw err;
^
Error: expect(received).toEqual(expected)
Expected value to equal:
[{"type": "START_CART_TRANSACTION_STARTED"}, {"payload": [{"message": "404 - Error"}], "success": false, "type": "START_CART_TRANSACTION_FINISHED"}]
Received:
[{"type": "START_CART_TRANSACTION_STARTED"}, {"payload": [[Error: Request failed with status code 404]], "success": false, "type": "START_CART_TRANSACTION_FINISHED"}]
Difference:
- Expected
+ Received
## -2,13 +2,11 ##
Object {
"type": "START_CART_TRANSACTION_STARTED",
},
Object {
"payload": Array [
- Object {
- "message": "404 - Error",
- },
+ [Error: Request failed with status code 404],
],
"success": false,
"type": "START_CART_TRANSACTION_FINISHED",
},
]
at request.respondWith.then (C:\dev\reactjs\OmniUI\src\tests\testing.test.js:101:27)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Is there a way for the asynchronous tests either (first example) to pass done but notify Jest that it failed? or setup jest to dont stop further tests on timeout?
You can call done with a parameter telling Jest the test has failed:
done(error);
I'm trying to test the GET HTTP method from a requests module:
const get = (host, resource, options) => {
...
return new Promise((resolve, reject) => fetch(url, opts)
.then(response => {
if (response.status >= 400) {
reject({
message: `[API request error] response status: ${response.status}`,
status: response.status });
}
resolve(response.json());
})
.catch(error => reject(error)));
};
And here is how I tested the .then part:
it('Wrong request should return a 400 error ', (done) => {
let options = { <parameter>: <wrong value> };
let errorJsonResponse = {
message: '[API request error] response status: 400',
status: 400,
};
let result = {};
result = get(params.hosts.api, endPoints.PRODUCTS, options);
result
.then(function (data) {
should.fail();
done();
},
function (error) {
expect(error).to.not.be.null;
expect(error).to.not.be.undefined;
expect(error).to.be.json;
expect(error).to.be.jsonSchema(errorJsonResponse);
done();
}
);
});
However I didn't find a way to test the catch part (when it gives an error and the response status is not >= 400).
Any suggestions?
It would also help me solve the problem a simple example with another code that tests the catch part of a Promise.
I've ended up writing the following code in order to test the catch:
it('Should return an error with invalid protocol', (done) => {
const host = 'foo://<host>';
const errorMessage = 'only http(s) protocols are supported';
let result = {};
result = get(host, endPoints.PRODUCTS);
result
.then(
() => {
should.fail();
done();
},
(error) => {
expect(error).to.not.be.null;
expect(error).to.not.be.undefined;
expect(error.message).to.equal(errorMessage);
done();
}
);
});
I want to test this function:
register(): void {
let user: User = new User();
user.username = this.username.value;
user.email = this.email.value;
user.password = this.password.value;
this._authService.register(user)
.map(rsp => rsp.json())
.subscribe((response) => { //
this._router.parent.navigate(["Login"]); //
}, (error) => {
this.responseError = JSON.parse(error._body).message;
}, () => {
this._authService.login(user)
.map(rsp => rsp.json())
.subscribe((data: any) => { //
this._authService.handleSuccessLogin(data, user);
this._router.parent.navigate(["../Game"]);
});
});
}
My _authService using http but I want to fake that call. I have tried to call through in it and mocking the http, but even if my response was 4xx it ran on the success part. Is it possible to test the error part somehow?
To simulate an error, you need to use the mockError method of the MockConnection. It accepts an Error object as parameter not a Response one. To be able to provide hints like status code, you could extend the Error class like that:
class ResponseError extends Error {
status: number;
(...)
}
and use it this way:
it('Should return something', inject([XHRBackend, HttpService, Injector], (mockBackend, httpService, injector) => {
mockBackend.connections.subscribe(
(connection: MockConnection) => {
if (connection.request.url === 'file1.json') {
var err = new ResponseError();
err.status = 404;
connection.mockError(err);
(...)
In this case, an error is thrown in your data flow and gotten for example into the second callback specified in the subcribe method.