Testing Angular Services with MSW causes timeout - node.js

EDIT: I should mention, that I only have problems during testing. When I run ng serve and use msw to serve the data everything works correctly.
I stumbled upon mswjs recently and wanted to use the mock service workers to test my frontend services without waiting on the backend team and avoid having to write mock-service classes. I setup everything according to the examples provided in the documentation.
At first I got the message that stating spec 'UserService should get list of users' has no expectations.
I researched this and added a done() function call at the end of my subscribe callback. After doing that, I get the following error:
Error: Timeout - Async function did not complete within 3000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL) in node_modules/jasmine-core/lib/jasmine-core/jasmine.js (line 7609)
I already tried increasing the default_timout in Karma but even setting it to 30.000 did not change the result.
I also tried working around by using waitForAsync without any success. This way I get no error and the test succeeds but only because it still finds no expectations within the spec.
Most example I found online do not deal with mock service workers and instead resort to using mock-services and fakeasync which does not help in my case.
This is how my code looks like:
My Angular Service:
#Injectable({
providedIn: 'root'
})
export class UserService {
private url = 'http://localhost:3000/api/users';
constructor(private http: HttpClient) { }
getUser(id: string): Observable<User> {
return this.http.get<User>(`${this.url}/${id}`);
}
listUsers(): Observable<User[]> {
return this.http.get<User[]>(this.url);
}
}
My Test Code:
describe('UserService', () => {
let service: UserService;
beforeAll(() => {
worker.start();
});
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
});
service = TestBed.inject(UserService);
});
afterAll(() => {
worker.stop();
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should get list of users', (done) => {
service.listUsers().subscribe((data) => {
expect(data.length).toBe(5);
done();
});
})
});
The Worker setup:
const handlers = [
rest.get('http://localhost:3000/api/users', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json(users))
})
]
export const worker = setupWorker(...handlers)

I managed to solve my own problem by using firstValueFrom and waitForAsync.
I changed the code in my tests to the following:
it('should get list of users', waitForAsync(async () => {
const source$ = service.listUsers();
const result = await firstValueFrom(source$);
expect(result.length).toEqual(5);
}));

Related

How to mock a function only once in a test suite?

I'm writing integration tests for a project. Within one test suite, I'm invoking a register endpoint in multiple tests. Most of the time I want to test what the actual response of the registerUser function is given certain req parameters.
This all works fine except I also want to test what happens if the registerUser function throws an error. I know I can mock the registerUser function on top of the test suite but this will affect all tests. I've tried to play around with jest.mock and jest.spyOn but I could not get it to work yet.
How can I mock the response of the registerUser function once and restore it afterwards so it doesn't affect the other tests in the suite?
authController.js
router.post('/register', async (req, res) => {
try {
const response = await registerUser(req);
res.status(HttpStatus.OK).json({ response });
} catch (err) {
res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ err });
}
});
authController.test.js
const faker = require('faker');
const HttpStatus = require('http-status-codes');
const authService = require('../services/authService');
// -- Tests where the response of the registerUser function are not mocked are here -- //
it('Gives a status code 500 when an unexpected error is thrown', async () => {
const registerUserMock = jest.spyOn(authService, "registerUser");
registerUserMock.mockReturnValue(() => new Error('Oh snap! Something went wrong.'));
const res = await agent.post('/register')
.send({
email: faker.internet.email(),
firstname: faker.name.firstName(),
lastname: faker.name.lastName(),
password: '123',
reTypedPassword: '123',
});
expect(res.statusCode).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
registerUserMock.mockRestore();
});
// -- more tests -- //
Easiest way would be to
group the tests which should use the same mocked response in a suite (describe)
mock the response in that suite's beforeAll hook and save the mock instance
restore the original implementation in that suite's afterAll hook.
describe('tests with successful auth result', () => {
let authSpy;
beforeAll(() => {
authSpy = jest.spyOn(authService, "registerUser").mockReturnValue(...);
});
afterAll(() => {
authSpy.mockRestore();
});
// tests using successful result
});
describe('tests with failing auth result', () => {
// same but with different spy result
});
note two important things:
you need to call mockRestore on the mock instance returned from mockReturnValue, not on the initial spy value
it's best to setup the mock in beforeEach / beforeAll and restore it in afterEach /afterAll, because if you set and restore it directly in the test (it), then if the test fails the spy remains unrestored, and may affect the following tests!

How to test a controller in Node.js using Chai, Sinon, Mocha

I've been learning how to write better unit tests. I am working on a project where controllers follow the style of 'MyController' shown below. Basically an 'async' function that 'awaits' on many external calls and returns a status with the results. I have written a very basic test for an inner function 'dbResults'. However, I am unsure of how to go about testing if the entire controller function itself returns a certain value. In the example below. I was wondering if anyone can help me figure out what is the proper way to test the final result of a function such as 'getUserWithId'. The code written below is very close but not exactly what I have implemented. Any suggestions would be appreciated.
Controller
export const MyController = {
async getUserWithId(req, res) {
let dbResults = await db.getOneUser(req.query.id);
return res.status(200).json({ status: 'OK', data: dbResults });
}
}
Current Test
describe ('MyController Test', () => {
describe ('getUserWithId should return 200', () => {
before(() => {
// create DB stub
});
after(() => {
// restore DB stub
});
it('should return status 200', async () => {
req = // req stub
res = // res stub
const result = await MyController.getUserWithId(req, res);
expect(res.status).to.equal(200);
});
});
});
I would suggest using an integration-test for testing your controller rather than a unit-test.
integration-test threats the app as a black box where the network is the input (HTTP request) and 3rd party services as dependency that should be mocked (DB).
Use unit-test for services, factories, and utils.
Use integration-test for external interfaces like HTTP and WebSockets
You can add e2e-test as well but if you have only one component in your setup you integration-test will suffice.

How to stub a service with Sinon in an integration test?

I'm trying to do some integration tests for my api in express.
My API's structure is something like:
app -> routes -> controllers -> services
Because I already have unit tests, my idea is only test that all that components are connected in the correct way.
So my idea was created an stub with Sinon for the service, and only check the responses of the controller with supertest.
When I run a single test everything is ok. The problem is when I run more than one unit test for different controllers, the stub doesn't work in the second run.
I think it's because the app is already saved in cache as a module, so sinon can't stub the service.
Some examples of my code:
controller.js
const httpStatus = require('http-status');
const { service } = require('../services/croupier');
/**
* Execute lambda tasks for candidates
* #public
*/
exports.task = async (req, res, next) => {
try {
const result = await service({
body: req.body,
authorizer: req.authorizer
});
console.log('res', result);
res.status(httpStatus.OK).json(result);
} catch (error) {
next(error);
}
};
foo.integration.test.js
const request = require('supertest');
const httpStatus = require('http-status');
const sinon = require('sinon');
const mongoose = require('../../../database');
const deleteModule = module => delete require.cache[require.resolve(module)];
const requireUncached = module => {
deleteModule(module);
return require(module);
};
describe('Foo - Integration Test', async () => {
describe('POST /v1/foo', () => {
const fooService = require('../../services/foo');
const stub = sinon.stub(fooService, 'service');
let db;
before(async () => {
db = await mongoose.connect();
});
afterEach(async () => {
sinon.restore();
});
after(async () => {
await db.close();
});
it('the api should response successfully', async () => {
stub.returns({});
const payload = { task: 'task', payload: [{ pathParameters: {}, body: {} }] };
const app = requireUncached('../../../app');
await request(app)
.post('/api/foo')
.send(payload)
.expect(httpStatus.OK);
});
it('the api should response with an error', async () => {
stub.throwsException();
const payload = { task: 'task', payload: [{ pathParameters: {}, body: {} }] };
const app = requireUncached('../../../app');
await request(app)
.post('/api/foo')
.send(payload)
.expect(httpStatus.INTERNAL_SERVER_ERROR);
});
});
});
The other integration tests have the same structure. I've also tried using proxyquire but didn't work.
Also I tried deleting cache of de app.js with any success.
Any ideas?
Context: integration test.
I agree with your idea: "test that all that components are connected in the correct way". Then what you need is spy, not stub. When there is a case / condition, you need to setup preconfigured/dummy data (up mongodb with specific data), turn on HTTP server, call HTTP request with specific data (post / get with specific query), and check the HTTP response for correct status, etc. The spy needed to check/validate/verify whether your service get called with correct parameter and response with correct result. This test validate you have correctly configured route - controller to a service for specific HTTP request.
You must have question: How to test negative scenario? For example: 404, 500. Then you need to know which specific scenario do what, which result negative condition. For example: if request come with unknown ID query, then response will be 404. Or if express not connected to database, then response will be 500. You need to know the real scenario, and again provide the require setup to produce the negative response.
For problem: "When I run a single test everything is ok. The problem is when I run more than one unit test for different controllers, the stub doesn't work in the second run.". There are several possible solutions, the main point is: you must make sure that the conditions for specific scenario/case are correctly prepared.
You can do:
create sandbox, to make sure no other stub service run between test cases.
start up fresh http (and or db) server before and shut down the server after the test run for each services, (for example: start the app and use real http client - as alternative to supertest)
run on debug mode to find out why the second stub not run or not get called or not work,
change implementation from stub to spy, you have already had a unit test, you just need to check whether the service get called or not, and then check the overall response.
Hope this helps.

Axios 'GET' request is pending but it never reaches the server

I am using axios on my React app to get data from my server (Node). My GET request stays pending in chrome developer tools and does not reach the server if I refresh the app on the provided route (e.g., http://localhost:3000/category/5d68936732d1180004e516cb). However, if I redirect from the home page, it will work.
I have tried different variations of making sure I end my responses on the server end of things.
Several posts have had related problems (e.g., request not reaching the server, POST request does not reach the server) but unfortunately not been helpful in my situation.
Here is my main call in my react app:
componentDidMount() {
console.log('I am here!'); // this gets executed even on page refresh
axios.get(`/api/categories/${this.props.id}`)
.then( (res) => {
this.setState({
title: res.data.category.title,
module: res.data.category.module ? true : false,
...res.data
})
}, (err) => console.log(err))
.catch(err => console.log(err));
}
On my back end I call this function after going through user verification:
module.exports.publishedCategories = async function(req, res) {
try {
// some code that I removed for clarity
res.json({
category,
children,
flagged: flaggedCategories
});
} catch(err) {
console.log(err);
res.sendStatus(500).end();
}
}
Some more code regarding my routing:
index.js
<Route
path='/category/:id'
render={ (props) => {
return <Category id={props.match.params.id} />
}}
/>
I do not get any error messages...
I was overzealous with my initial solution of switching to componentDidUpdate(). This only worked for page refreshes but not for redirects (i.e., I had the reverse problem). My final solution is as follows:
componentDidMount = async () => {
setTimeout( async () => {
this.loadCategory();
}, 10)
}
componentDidUpdate = async (props, state) => {
if(props.match.params.id !== this.props.match.params.id) {
this.loadCategory();
return;
}
return false;
}
loadCategory = async () => {
let result = await axios.get(`/api/categories/${this.props.match.params.id}`);
this.setState({
title: result.data.category.title,
module: result.data.category.module ? true : false,
...result.data
});
}
I am not sure why adding a setTimeout() works for componentDidMount().
As per the documentation componentDidUpdate(), it is good for netork updates however you need to compare prior props with current props.
Unforunately I am not sure how to get around the setTimeout() hack but it seems to work.

Running multiple mocha files and suites with same context

I'm trying to run several integration tests with some shared context. The context being shared is a single express application, and I'm trying to share it across suites / files because it takes a few seconds to spin up.
I got it to work by instantiating a "runner" mocha test suite, that would have test functions that would just require each test file as needed, and this was working well (a side effect is that the test requiring the child test file would finish as "success" before any of the tests inside the file would actually run, but this was a minor issue)
// test-runner.js:
describe('Integration tests', function () {
let app
let log
this.timeout(300000) // 5 mins
before(function (done) {
app = require('../app')
app.initialize()
.then(() => {
done()
})
.catch(err => {
log.error(err)
done(err)
})
})
it('Running api tests...', (done) => {
require('./integration/api.test')(app)
done()
})
// ./integration/api.test.js:
module.exports = (app) => {
let api = supertest.agent(app)
.set('Content-Type', 'application/json')
describe('Authorization', () => {
describe('Trying to access authorization sections', () => {
it('should be denied for /home', async () => {
await api.get(`${baseUrl}/home`)
.expect(STATUS_CODE.UNAUTHORIZED)
})
...
The Problem:
I want to signal the test runner that all of the tests in the imported suite have finished, so I can call shutdown logic in the test runner and end the test cleanly. In standard test functions, you can pass a done function to signal that the code in the test is complete, so I wrapped each of the child tests in a describe block to use the after hook to signal that the whole test module was done:
// test-runner.js:
describe('Integration tests', function () {
let app
let log
this.timeout(300000) // 5 mins
before(function (done) {
app = require('../app')
app.initialize()
.then(() => {
done()
})
.catch(err => {
log.error(err)
done(err)
})
})
it('Running api tests...', (done) => {
require('./integration/api.test')(app, done)
})
// ./integration/api.test.js:
module.exports = (app, done) => {
let api = supertest.agent(app)
.set('Content-Type', 'application/json')
describe('All api tests', () => {
let api
before(() => {
api = supertest.agent(app)
.set('Content-Type', 'application/json')
})
after(() => {
done() // should be calling the done function passed in by test runner
})
describe('Authorization', () => {
describe('Trying to access authorization sections', () => {
it('should be denied for /home', async () => {
await api.get(`${baseUrl}/home`)
.expect(STATUS_CODE.UNAUTHORIZED)
})
...
but when I do this, the test suites just don't run. The default timeout will just expire, and if I set a higher timeout, it just sits there (waiting for the longer timeout). If I hook into a debug session, then the test exits immediately, and the after hook (and before!) never get called.
I'm open to other ideas on how to do this as well, but I haven't found any good solutions that that allow sharing some context between tests, while having them broken into different files.

Resources