I'm working with Mocha.js for testing in a Node.js - Express.js - Firebase
I need a token from Firebase to access the API endpoints, I have a before hook in all my files, but after about 250 tests, probably calling the authentication endpoint multiple times, I'm getting rate limited by firebase.
I want to get the token once and use it in all my tests.
The tests are spread in different files, I have an index.js that requires them all.
I'm aware of Root Level Hooks, but how can I save the token and use it in all my separate files?
Thanks!
you can create a function that gets the token. then call it. then create your test suite only after that
function getToken(callback) {
//
}
// define tests
function allTests(token) {
describe(xxxxxx, function () {
it(xxxxxxxxx, function() {
//
})
});
}
// start all
getToken(function(token) {
allTests(token);
});
I managed to solve it myself, if anyone needs an answer on how to approach it, take a look at this.
I have multiple files where we write our unit testing, we unite them in an index.spec.js that we execute for testing ($ mocha index.spec.js)
I created a utility file that looks like this:
let token;
(() => { token = getYourToken() })()
module.exports = {
getToken: () => {
return new Promise((resolve) => {
const interval = setInterval(() => {
if (token) {
clearInterval(interval);
resolve(token);
}
}, 100);
});
}
};
Basically, it's a singleton, in the index.spec.js I require this file, executing the 'getYourToken()' once (add your logic to get token here). Then I store it in a variable that then I export.
In the export, I use an interval because my current code is not using promises, use your best practice method, interval + Promise worked for me.
This way I require this file in my tests and get the token I got at the beginning once, avoiding rate-limiting and any issue with firebase.
Create a JSON file in your test root directory.
Import the file.
Append a token property with the token value.
Then import it anywhere to access the token property .
Related
This answer to a similar question does a great job at explaining how fastify-plugin works and what it does. After reading the explanation, I still have a question remaining; how is this different from a normal function call instead of using the .register() method?
To clarify with an example, how are the two approaches below different from each other:
const app = fastify();
// Register a fastify-plugin that decorates app
const myPlugin = fp((app: FastifyInstance) => {
app.decorate('example', 10);
});
app.register(myPlugin);
// Just decorate the app directly
const decorateApp = (app: FastifyInstance) => {
app.decorate('example', 10);
};
decorateApp(app);
By writing a decorateApp function you are creating your own "API" to load your application.
That said, the first burden you will face soon is sync or async:
decorateApp is a sync function
decorateAppAsync within an async function
For example, you need to preload something from the database before you can start your application.
const decorateApp = (app) => {
app.register(require('#fastify/mongodb'))
};
const businessLogic = async (app) => {
const data = await app.mongo.db.collection('data').find({}).toArray()
}
decorateApp(app)
businessLogic(app) // whoops: it is async
In this example you need to change a lot of code:
the decorateApp function must be async
the mongodb registration must be awaited
the main code that loads the application must be async
Instead, by using the fastify's approach, you need to update only the plugin that loads the database:
const applicationConfigPlugin = fp(
+ async function (fastify) {
- function (fastify, opts, next) {
- app.register(require('#fastify/mongodb'))
- next()
+ await app.register(require('#fastify/mongodb'))
}
)
PS: note that fastify-plugin example code misses the next callback since it is a sync function.
The next bad pattern will be high hidden coupling between functions.
Every application needs a config. Usually, the fastify instance is decorated with it.
So, you will have something like:
decorateAppWithConfig(app);
decorateAppWithSomethingElse(app);
Now, decorateAppWithSomethingElse will need to know that it is loaded after decorateAppWithConfig.
Instead, by using the fastify-plugin, you can write:
const applicationConfigPlugin = fp(
async function (fastify) {
fastify.decorate('config', 42);
},
{
name: 'my-app-config',
}
)
const applicationBusinessLogic = fp(
async function (fastify) {
// ...
},
{
name: 'my-app-business-logic',
dependencies: ['my-app-config']
}
)
// note that the WRONG order of the plugins
app.register(applicationBusinessLogic);
app.register(applicationConfigPlugin);
Now, you will get a nice error, instead of a Cannot read properties of undefined when the config decorator is missing:
AssertionError [ERR_ASSERTION]: The dependency 'my-app-config' of plugin 'my-app-business-logic' is not registered
So, basically writing a series of functions that use/decorate the fastify instance is doable but it adds
a new convention to your code that will have to manage the loading of the plugins.
This job is already implemented by fastify and the fastify-plugin adds many validation checks to it.
So, by considering the question's example: there is no difference, but using that approach to a bigger application
will lead to a more complex code:
sync/async loading functions
poor error messages
hidden dependencies instead of explicit ones
I'm currently implementind OpenID/OAuth authorization in my project and using openid-client package for that.
In this package we initialize an openid client with the following code:
const { Issuer } = require('openid-client');
Issuer.discover('https://accounts.google.com') // => Promise
.then(function (googleIssuer) {
console.log('Discovered issuer %s %O', googleIssuer.issuer, googleIssuer.metadata);
});
const client = new googleIssuer.Client({
client_id: 'zELcpfANLqY7Oqas',
client_secret: 'TQV5U29k1gHibH5bx1layBo0OSAvAbRT3UYW3EWrSYBB5swxjVfWUa1BS8lqzxG/0v9wruMcrGadany3',
redirect_uris: ['http://localhost:3000/cb'],
response_types: ['code'],
// id_token_signed_response_alg (default "RS256")
// token_endpoint_auth_method (default "client_secret_basic")
}); // => Client
How we can implement a singleton client logic?
To create a client only once and reuse it all over the application?
I've tried to create a separate class for that but not sure if it is correct:
import { Issuer } from 'openid-client';
export class OpenIdClient {
async createOpenIdClient() {
const issuer = await Issuer.discover(encodeURI(`http://localhost:3101/.well-knownendpoint`));
const client = await new issuer.Client({
client_id: 'clientId',
client_secret: 'clientSecret'
})
return client;
}
}
You're close here, and using a class can be a solution, though I find classes to be less useful if all you're after is a singleton. Classes are great when you want to instance more than one time (or at least have the ability to).
I usually just use a plain variable or object for a singleton. No need to use anything more complicated here.
Note there are other answers for how to create singletons, so this is just my own opinion on the best/simplest solution for your needs.
In a separate file (e.g. oauth-client.js):
// oauth-client.js
const { Issuer } = require('openid-client');
Issuer.discover('https://accounts.google.com')
.then(function (googleIssuer) {
console.log('Discovered issuer %s %O', googleIssuer.issuer, googleIssuer.metadata);
});
const client = new googleIssuer.Client({
client_id: 'redacted',
client_secret: 'redacted',
redirect_uris: ['http://localhost:3000/cb'],
response_types: ['code'],
});
module.exports = client;
The first time it's loaded by a file, it'll instantiate. Any subsequent times it's loaded in the node.js app, it won't re-instantiate. It'll reuse the entity that's already in memory.
//other file, let's pretend in the same file path.
const oauthClient = require('./oauth-client');
...
If you're wanting to make sure for yourself, put a log line at the top of the singleton file. Then load the file from two different parts of your app. You'll see that it only logs it out once.
To answer the question in the comments below: Swap these lines for an object destructured approach, which makes it nice for if you want to export multiple things from the file:
...
module.exports = { oauthClient: client }
const { oauthClient } = require('./oauth-client');
...
Since we aren't exporting more than one thing (the client), the original answer is still fine.
I am using Jest to test my API and when I run my tests, my JSON file results.json gets written to due to the following line in my API app.js (which I don't want happening):
fs.writeFile('results.json', JSON.stringify(json), (err, result) => {
if (err) console.log('error', err);
});
This is what my Jest file looks like:
const request = require('supertest');
const app = require('./app');
// Nico Tejera at https://stackoverflow.com/questions/1714786/query-string-encoding-of-a-javascript-object
function serialise(obj){
return Object.keys(obj).map(k => `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`).join('&');
}
describe('Test /addtask', () => {
test('POST /addtask Successfully redirects if newDate and newTask filled in correctly', () => {
const params = {
newTask: 'Example',
newDate: '2020-03-11'
};
return request(app)
.post('/addtask')
.send(serialise(params))
.expect(301);
});
});
I tried creating a mock of the JSON file and placed it outside the describe statement to prevent the actual results.json file being written to:
jest.mock('./results.json', () => ({ name: 'preset1', JSONtask: [], JSONcomplete: [] }, { name: 'preset2', JSONtask: [], JSONcomplete: [] }));
But this doesn't change anything. Does anyone have any suggestions?
I have seen other solutions to similar problems but they don't provide the answer I'm looking for.
EDIT: Although not a very good method, one solution to my problem is to wrap the fs.writeFile within the statement
if (process.env.NODE_ENV !== 'test') {
//code
};
although this would mean that fs.writeFile cannot be tested upon.
NOTE: I am still accepting answers!
Your issue is that the code you want to test has a hard-coded I/O operation in it, which always makes things harder to test.
What you'll want to do is to isolate the dependency on fs.writeFile, for example into something like a ResultsWriter. That dependency can then be injected and mocked for your test purposes.
I wrote an extensive example on a very similar case with NestJS yesterday under how to unit test a class extending an abstract class reading environment variables, which you can hopefully adapt to your needs.
jest.mock(path, factory) is for mocking JS modules, not file content.
You should instead mock fs.writeFile and check that it has been called with the expected arguments. The docs explain how to do it.
The thing is that I have some preparations to do before starting tests. I've got several test files and each of them requires rabbit connection to be opened before each test file, and close it after the ending of all tests in every file. The thing is that I've got to duplicate code in test files to open and close connection. How can deal with connection in one file. I am using TypeScript so several solutions as jest-environment-node, jest-set are not helpful in this case. Also there is a chance that I can solve this proble using setup files as:
"setupFiles": [
"./src/inttests/partial/mocks/setup.ts"
],
And write in setup file something like:
let channel: PubChannel;
beforeAll(() => {
channel = new PubChannel('amqp://localhost', true);
exports.ss = new SystemService(channel); // or using globals but it doesn't actually work in both cases.
});
afterAll(async () => {
await channel.closeConnection();
}
);
The thing is that I need ss variable to use in tests in order to call some functions. But I don't know how to make them available from setupt.ts file as I can't use export kew word because the value has been modified inside beforeAll scope. Is there any chance to solve this ridiculous problem?
With RequireJS on the front-end, we can listen to see when modules get loaded into the runtime module cache using:
requirejs.onResourceLoad = function (context, map, depArray) {
console.log('onResourceLoad>>>', 'map.id:', map.id, 'context:', context);
};
Can we do this with Node.js somehow? Will be useful for debugging. Especially when servers are loading different files (or in different order) based on configuration.
I assume this might be documented in
https://nodejs.org/api/modules.html
but I am not seeing anything
If you look at the source code for require(), you will find this:
Module._load = function(request, parent, isMain) {
if (parent) {
debug('Module._load REQUEST %s parent: %s', request, parent.id);
}
This shows that you can leverage the debug() call to get the information you need. In order to do this, you will notice that module is setup using util.debuglog('module'). This means that you need to run your application with with the NODE_DEBUG variable set to module. You can do it the following way from the console:
NODE_DEBUG=module node main.js
This will log what you are looking for.
I'm not aware of a documented callback API for the purpose of module load callbacks (although a logging mechanism for module loading appears to exist).
Here's a quick workaround to the apparent lack of such a callback, by monkeypatching Module._load:
const Module = require('module');
const originalModuleLoad = Module._load;
Module._load = function() {
originalModuleLoad.apply(this, arguments);
console.log("Loaded with arguments " + JSON.stringify(arguments));
}
I executed the above code in a REPL and then did require('assert'). Lo and behold:
> require('assert')
Loading with arguments {"0":"assert","1":{"id":"<repl>","exports":{},"filename":null,"loaded":false,"children":[],"paths":["/Users/mz2/Projects/manuscripts-endnote-promo/repl/node_modules","/Users/mz2/Projects/manuscripts-endnote-promo/node_modules","/Users/mz2/Projects/node_modules","/Users/mz2/node_modules","/Users/node_modules","/Users/mz2/.nvm-fish/v6.1.0/lib/node_modules","/Users/mz2/.node_modules","/Users/mz2/.node_libraries","/Users/mz2/.nvm-fish/v6.1.0/lib/node"]},"2":false}
Please don't think about using code like above for anything but debug only purposes.
Because node.js modules are imported (required) synchronously, simply having the require statement means the module is imported.
While RequireJS can import modules asynchronously, the even listening is an important feature, but native require in Node.js leaves this necessity out. This way, as you probably know:
const module = require('module')
// You can use the module here, async or sync.
To add to that, not only require is sync, but also in order to use a module it has to be explicitly required in the same file where it's used. This can be bypassed in several ways, but best practice is to require in every module where you use a module.
For specific modules which require async initialization, either the module should provide an event, or you can wrap the init function using a promise or a callback. For example, using a promise:
const module = require('module')
// Create a promise to initialize the module inside it:
const initialized = new Promise((resolve, reject) => {
// Init module inside the promise:
module.init((error) => {
if(error){
return reject(error)
}
// Resolve will indicate successful init:
resolve()
})
})
// Now with wrapped init, proceed when done:
initialized
.then(() => {
// Module is initialized, do what you need.
})
.catch(error => {
// Handle init error.
})