Multiple tests for RESTfull API with Chai and Moca - connecting to server - mocha.js

I'm trying to test my API and I'm having the following error:
Uncaught Error: listen EADDRINUSE: address already in use :::5000
this is my code:
test.ts
chai.use(chaiHttp)
describe('Persons', () => {
describe('GET /person/', () => {
it('check error path', (done) => {
chai
.request(startServer)
.get("/persons/")
.end((err, res) => {
res.should.have.status(422);
})
done();
});
});
// describe('GET /person/:firstName', () => {
it('It should person by first name', (done) => {
chai
.request(startServer)
.get("/persons/Ross")
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a('string');
// res.header["content-type"].should.contains('application/json');
})
done();
});
// });
});
server.ts
import express, { Application } from "express";
import personRouter from "./route/person.route";
import groupRouter from "./route/group.route";
import bodyParser from "body-parser";
const app: Application = express();
const PORT: Number | string = process.env.NODE_ENV || 5000;
app.use(bodyParser.json());
app.use('/person', personRouter)
app.use('/group', groupRouter)
export function startServer() {
app.listen(PORT, () => {
console.log(`Server started on http://localhost:${PORT}`);
});
}
what am I doing wrong?

I ran the server from a diffrent place and then I did this:
describe('Persons', () => {
const host = "http://localhost:5000/person/";
describe('GET /person/', () => {
it('check error path', (done) => {
chai
.request(host)
.get("/persons/")
.end((err, res) => {
res.should.have.status(200);
})
done();
});
});
This way the server is not trying to start evry test.

Related

Cypress - Adding handelExpection code but still can't catch the app exceptions

I'm trying to test this site https://store.google.com/regionpicker and when I select a region (US) for example and get redirected to this url: https://store.google.com/us/?hl=en-US&regionRedirect=true I get an app error in the console, and cypress fails to continue
I added this code to handle exceptions before the test starts, but it doesn't catch it:
cy.on('uncaught:exception', () => {
return false
})
full code:
describe('Purchase a device from Google Store', () => {
const LOCATORS = {
devicesMenu: '[id="desktop-products"]',
headerContainer: '.header-container',
regionTitle: '.region-title',
dialog: '[role="dialog"]',
button: '[role="button"]',
}
before(() => {
cy.visit('/')
})
beforeEach(() => {
Cypress.on('uncaught:exception', () => {
return false
})
})
it('Purchase device', () => {
let region = 'United States'
let urlToVerify = 'us/?hl=en-US&regionRedirect=true'
cy.contains(LOCATORS.regionTitle, region)
.click().then(() => {
cy.get(LOCATORS.dialog).within(() => {
cy.contains('Continue')
.click({ force: true }).then(() => {
cy.url()
.should('include', urlToVerify)
})
})
})
})
})
You can test the URL in an event handler for the url:changed event.
The basic problem seems to be that it's not possible to take Cypress to https://store.google.com, the browser ends up with URL about:blank.
But by adding a listener to url:changed you can at least verify that the correct URL was attempted.
it('redirects after region select', () => {
const region = 'United States'
const urlToVerify = 'us/?hl=en-US&regionRedirect=true'
cy.on('url:changed', (url) => {
expect(url).to.include(urlToVerify) // use chai to assert
})
cy.contains(LOCATORS.regionTitle, region).click()
cy.get(LOCATORS.dialog).within(() => {
cy.contains('Continue').click({ force: true })
})
})

How to use Socket.io with Next.js API Routes

Next.js provides serverless API routes. By creating a file under ./pages/api you can have your service running, and I want to have a Socket.io service by using this mechanism.
I have created a client:
./pages/client.js
import { useEffect } from 'react';
import io from 'socket.io-client';
export default () => {
useEffect(() => {
io('http://localhost:3000', { path: '/api/filename' });
}, []);
return <h1>Socket.io</h1>;
}
And an API route:
./pages/api/filename.js
const io = require('socket.io')({ path: '/api/filename' });
io.onconnection = () => {
console.log('onconnection');
}
io.on('connect', () => {
console.log('connect');
})
io.on('connection', () => {
console.log('connection');
})
export default (req, res) => {
console.log('endpoint');
}
But I can't get the client to connect to the Socket.io server and succesfully see any of: 'onconnection', 'connect', or 'connection' printed.
The trick is to plug 'socket.io' into the http server only once, so checking every access to the api.
Try something like this:
./pages/api/socketio.js
import { Server } from 'socket.io'
const ioHandler = (req, res) => {
if (!res.socket.server.io) {
console.log('*First use, starting socket.io')
const io = new Server(res.socket.server)
io.on('connection', socket => {
socket.broadcast.emit('a user connected')
socket.on('hello', msg => {
socket.emit('hello', 'world!')
})
})
res.socket.server.io = io
} else {
console.log('socket.io already running')
}
res.end()
}
export const config = {
api: {
bodyParser: false
}
}
export default ioHandler
./pages/socketio.jsx
import { useEffect } from 'react'
import io from 'socket.io-client'
export default () => {
useEffect(() => {
fetch('/api/socketio').finally(() => {
const socket = io()
socket.on('connect', () => {
console.log('connect')
socket.emit('hello')
})
socket.on('hello', data => {
console.log('hello', data)
})
socket.on('a user connected', () => {
console.log('a user connected')
})
socket.on('disconnect', () => {
console.log('disconnect')
})
})
}, []) // Added [] as useEffect filter so it will be executed only once, when component is mounted
return <h1>Socket.io</h1>
}
You have to have the /api/pusher/auth to authenticate with pusher on the frontend. Then you use the key you get from that to communicate with pusher. It's for security purposes. You can do it all through the frontend, but depending on your app, if you're saving data (such as messages, or chats) then probably should authenticate.
You can use custom server and attach sockets to it (just like with express) and provide needed path where socket.io will listen. How to use custom server
You can write something like this server.js
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const { Server } = require('socket.io');
const dev = process.env.NODE_ENV !== 'production';
const hostname = 'localhost';
const port = 3000;
// when using middleware `hostname` and `port` must be provided below
const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = createServer(async (req, res) => {
try {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true);
const { pathname, query } = parsedUrl;
if (pathname === '/a') {
await app.render(req, res, '/a', query);
} else if (pathname === '/b') {
await app.render(req, res, '/b', query);
} else {
await handle(req, res, parsedUrl);
}
} catch (err) {
console.error('Error occurred handling', req.url, err);
res.statusCode = 500;
res.end('internal server error');
}
});
const io = new Server(server, {
path: '/socket.io' // or any other path you need
});
io.on('connection', socket => {
// your sockets here
console.log('IO_CONNECTION');
});
server.listen(port, err => {
if (err) throw err;
console.log(`> Ready on http://${hostname}:${port}`);
});
});
You would need to run your server using node server.js

sinon fake server.requests returns an empty array

I'm not sure if I'm doing it correctly, I just followed their documentation.
I'm using mocha + sinonjs
actions.js
import axios from 'axios';
export const attachmentActions = {
getAttachment(id) {
axios.get('/api/attachment/${id}')
.then(response => {
console.log(respons);
})
.catch(error => {
console.log(error);
})
}
}
export const actions = {
...attachmentActions,
}
actions.test.js
describe('actions-test', () => {
let server;
beforeEach(() => {
server = sinon.createFakeServer();
});
afterEach(() => {
server.restore();
})
it('call getAttachment', () => {
server.requests[0].respond(200,
{'Content-type':'application/json'},
JSON.stringify({mimeType: 'image/png', path: '/user/image/'}));
actions.getAttachment(1)
//assertion
})
})
With the code above I got TypeError: Cannot read property 'respond' of undefined
I also tried below
it('call getAttachment', () => {
server.respondWith('GET','/api/attachment/1',[200,
{'Content-type':'application/json'},
JSON.stringify({mimeType: 'image/png', path: '/user/image/'}))];
actions.getAttachment(1);
server.respond();
//assertion
})
but when I run the test axios returns 404
Would really appreciate if someone can pinpoint if I miss something?
sinon version: 7.3.2

Moxios Requests State Not Cleared In Between Tests

My specs are behaving weirdly in that when I run the tests alone, they pass. However, when I run the test suite all together, the failure tests still continue to use the success axios mock instead of using the correct failing http axios mock. This results in my tests failing. Am I missing something for isolating the 2 mocks from each other in the different portions of code?
jobactions.js
export const loadUnassignedJobs = (job_type) => {
if (!['unscheduled', 'overdue'].includes(job_type)) {
throw 'Job Type must be "unscheduled" or "overdue".';
}
return (dispatch) => {
dispatch({type: JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED, job_type });
return axios.get(defaults.baseapi_uri + 'jobs/' + job_type)
.then(function (response) {
dispatch(updateUnassignedJobs(response.data.jobs));
// handle success
})
.catch(function (error) {
// handle error
dispatch({ type: JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE, error });
})
.then(function () {
// always executed
});
}
};
export const updateUnassignedJobs = (unassigned_jobs) => {
let unassigned_job_ids = [];
let jobs = {};
for (let job of unassigned_jobs) {
unassigned_job_ids.push(job.id);
jobs[job.id]=job;
}
return({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS,
jobs,
unassigned_job_ids,
});
};
spec.js
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import * as jobActions from "../../../app/javascript/actions/JobActions"
import { findAction } from '../support/redux_store'
import * as JobActionTypes from '../../../app/javascript/constants/JobActionTypes'
import fixtures_jobs_unscheduled_success from '../fixtures/jobs_unscheduled_success'
import moxios from "moxios";
export const mockStore = configureMockStore([thunk]);
let store;
describe ('loadUnassignedJobs', () => {
context('when bad parameters are passed', async () => {
it('will raise an error', () => {
const store = mockStore();
expect(() => {
store.dispatch(jobActions.loadUnassignedJobs('wrong_type'));
}).to.throw('Job Type must be "unscheduled" or "overdue".');
});
});
context('when unscheduled is passed', () => {
beforeEach(() => {
moxios.install();
console.log("before each called");
console.log(moxios.requests);
store = mockStore();
store.clearActions();
});
afterEach(() => {
console.log("after each called");
console.log(moxios.requests);
moxios.uninstall();
});
context('on success', () => {
beforeEach(() => {
moxios.wait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: fixtures_jobs_unscheduled_success
});
});
})
it('dispatches LOAD_UNASSIGNED_JOBS_STARTED', () => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED)).to.be.eql({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED,
job_type: 'unscheduled'
});
});
});
it('dispatches updateUnassignedJobs()', () => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store,JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS)).to.be.eql(jobActions.updateUnassignedJobs(fixtures_jobs_unscheduled_success.jobs))
});
});
});
context('on error', () => {
beforeEach(() => {
//console.log("before each on error called");
//console.log(moxios.requests);
moxios.wait(() => {
console.log('after waiting for moxios..')
console.log(moxios.requests);
let request = moxios.requests.mostRecent();
request.respondWith({
status: 500,
response: { error: 'internal server error' }
});
});
})
it('dispatches LOAD_UNASSIGNED_JOBS_FAILURE', (done) => {
console.log(moxios.requests);
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
console.log(moxios.requests);
console.log(store.getActions());
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE)).to.include({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE
});
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE).error).to.include({
message: 'Request failed with status code 500'
});
done();
});
});
it('does not dispatch LOAD_UNASSIGNED_JOBS_SUCCESS', (done) => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS)).to.be.undefined;
done();
});
});
})
});
});
describe('updateUnassignedJobs', () => {
it('assigns jobs to hash and creates an unassigned_job_ids array', () => {
expect(jobActions.updateUnassignedJobs([ { id: 1, step_status: 'all_complete' }, { id: 2, step_status: 'not_started' } ])).to.be.eql(
{
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS,
jobs: { 1: { id: 1, step_status: 'all_complete' }, 2: { id: 2, step_status: 'not_started' } },
unassigned_job_ids: [ 1,2 ]
}
)
});
});
Found the issue!
The it() blocks for the success case were not using the done callback causing the afterEach() moxios.uninstall() to be called prematurely and not resetting the requests after the call was complete. Fixing this, and now all the tests pass.

How can I test Observable.ajax (redux-observable)?

I have been playing with rxjs and redux-observable for the last few days and have been struggle to find a way to a test for Observable.ajax. I have the following epic which create a request to https://jsonplaceholder.typicode.com/,
export function testApiEpic (action$) {
return action$.ofType(REQUEST)
.switchMap(action =>
Observable.ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
)
}
where,
export const REQUEST = 'my-app/testApi/REQUEST'
export const SUCCESS = 'my-app/testApi/SUCCESS'
export const FAILURE = 'my-app/testApi/FAILURE'
export const CLEAR = 'my-app/testApi/CLEAR'
export function requestTestApi () {
return { type: REQUEST }
}
export function successTestApi (response) {
return { type: SUCCESS, response }
}
export function failureTestApi (error) {
return { type: FAILURE, error }
}
export function clearTestApi () {
return { type: CLEAR }
}
The code works fine when runs in browser but not when testing with Jest.
I have try,
1) Create a test based on https://redux-observable.js.org/docs/recipes/WritingTests.html. The store.getActions() returns only { type: REQUEST }.
const epicMiddleware = createEpicMiddleware(testApiEpic)
const mockStore = configureMockStore([epicMiddleware])
describe.only('fetchUserEpic', () => {
let store
beforeEach(() => {
store = mockStore()
})
afterEach(() => {
epicMiddleware.replaceEpic(testApiEpic)
})
it('returns a response, () => {
store.dispatch({ type: REQUEST })
expect(store.getActions()).toEqual([
{ type: REQUEST },
{ type: SUCCESS, response }
])
})
})
2) Create a test based on Redux-observable: failed jest test for epic. It returns with
Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('returns a response', (done) => {
const action$ = ActionsObservable.of({ type: REQUEST })
const store = { getState: () => {} }
testApiEpic(action$, store)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
{ type: SUCCESS, response }
])
done()
})
})
Can someone point me out what is the correct way to test Observable.ajax ?
I would follow the second example, from StackOverflow. To make it work you'll need to make some minor adjustments. Instead of importing Observable.ajax in your epic file and using that reference directly, you need to use some form of dependency injection. One way is to provide it to the middleware when you create it.
import { ajax } from 'rxjs/observable/dom/ajax';
const epicMiddleware = createEpicMiddleware(rootEpic, {
dependencies: { ajax }
});
The object we passed as dependencies will be give to all epics as the third argument
export function testApiEpic (action$, store, { ajax }) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Alternatively, you could not use the dependencies option of the middleware and instead just use default parameters:
export function testApiEpic (action$, store, ajax = Observable.ajax) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Either one you choose, when we test the epic we can now call it directly and provide our own mock for it. Here are examples for success/error/cancel paths These are untested and might have issues, but should give you the general idea
it('handles success path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
successTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('handles error path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.throw({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
failureTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('supports cancellation', (done) => {
const action$ = ActionsObservable.of(requestTestApi(), clearTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method }).delay(100)
};
const onNext = chai.spy();
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe({
next: onNext,
complete: () => {
onNext.should.not.have.been.called();
done();
}
});
});
For the first way:
First, use isomorphic-fetch instead of Observable.ajax for nock support, like this
const fetchSomeData = (api: string, params: FetchDataParams) => {
const request = fetch(`${api}?${stringify(params)}`)
.then(res => res.json());
return Observable.from(request);
};
So my epic is:
const fetchDataEpic: Epic<GateAction, ImGateState> = action$ =>
action$
.ofType(FETCH_MODEL)
.mergeMap((action: FetchModel) =>
fetchDynamicData(action.url, action.params)
.map((payload: FetchedData) => fetchModelSucc(payload.data))
.catch(error => Observable.of(
fetchModelFail(error)
)));
Then, you may need an interval to decide when to finish the test.
describe("epics", () => {
let store: MockStore<{}>;
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
nock.cleanAll();
epicMiddleware.replaceEpic(epic);
});
it("fetch data model succ", () => {
const payload = {
code: 0,
data: someData,
header: {},
msg: "ok"
};
const params = {
data1: 100,
data2: "4"
};
const mock = nock("https://test.com")
.get("/test")
.query(params)
.reply(200, payload);
const go = new Promise((resolve) => {
store.dispatch({
type: FETCH_MODEL,
url: "https://test.com/test",
params
});
let interval: number;
interval = window.setInterval(() => {
if (mock.isDone()) {
clearInterval(interval);
resolve(store.getActions());
}
}, 20);
});
return expect(go).resolves.toEqual([
{
type: FETCH_MODEL,
url: "https://test.com/assignment",
params
},
{
type: FETCH_MODEL_SUCC,
data: somData
}
]);
});
});
enjoy it :)

Resources