Sometimes Jest integration tests fails and sometimes don't when I run all the suites - node.js

I've written some integration test using Jest, Supertest, Moongose. I run all test isolated (test.only) and they work but sometimes don't. Often this happens when I run the entire test suite. I give you an example: This test creates a new registry in a MongoDB collection and then other test use this new registry to perform another operations:
beforeAll(async () => {
await mongoose.connect(config.mongoose.url, config.mongoose.options);
});
afterAll(async () => {
await mongoose.disconnect();
await new Promise(resolve => setTimeout(() => resolve(), 500));
});
let credentials = [];
let fechaHora = [];
// generate a new Id registry
// generate credentials
// generate date and hour
beforeEach(async () => {
rooms.insertRoomsToFile(rooms.getNewIdRoom() + '|');
_idRoom = rooms.getIdRoom();
credentials = await rooms.generateCredentialsBE(_idOrganization, basicToken);
fechaHora = rooms.generateRoomDateAndHour();
});
test(`endpoint ${BASE_URL}${registerMeetingRoute} : 200 OK (Happy Path)`, async () => {
generatedIdRoom = _idRoom;
const data = {
idOrganization: 1,
idRoom: generatedIdRoom,
date: fechaHora[0],
hour: fechaHora[1],
attendes: [
{
email: "john.doe#example.com.mx",
id: 1,
firstName: "John",
lastName: "Doe",
userAttende: "10000000-0000-0000-0000-000000000000",
rol: 1,
telephone: "5555555555"
},
{
email: "tom.taylor#example.com.mx",
id: 2,
firstName: "Tom",
lastName: "Taylor",
userAttende: "20000000-0000-0000-0000-000000000000",
rol: 2,
telephone: "5555555556"
}
]
};
const encryptedData = await rooms.encrypt(data);
idAccess = encryptedData.idAccess;
await request(app)
.post(`${BASE_URL}${registerMeetingRoute}`)
.set('content-type', 'application/json')
.set('authorization', 'Bearer ' + credentials[2])
.set('x-accessId', idAccess)
.send(encryptedData)
.expect(200);
rooms.saveLog(JSON.stringify(encryptedData), `endpoint ${BASE_URL}${registerMeetingRoute} : 200 OK (Happy Path)`);
});
This works fine, the problem is sometimes don't. I've tried many answers here and read blogs about this topic but I can't solve it. I tried:
Increase testTimeout property in jest.config.js
Open and close MongoDb connection per test
To use mongodb-memory-server
--runInBand option
Thanks in advance :)

Jest will execute different test files in parallel and in a different order from run to run.
You can change this behaviour and write your own custom test sequencer.

Jest will execute all tests within a single file sequentially.
However, I've also encountered issues with the complete file run vs. running a single test, in case a plug-in a router with WebHashHistory into the wrapper mount. The problem does not occur with MemoryHistory:
const router = createRouter({
history: createMemoryHistory(),
routes: myRoutes,
})
Vue 3.2, Vue-router 4.0, vue-test-utils v2.0, options API, SFC, Jest v27.0

Related

Can I apply 'setupFilesAfterEnv' to specific file in jest config?

I'm working on appling prisma unit testing and Integration testing
I want to apply unit testing for *.service.test.ts files
and intergration testing for *.test.ts files.
I followed the Prisma document, but there is something that doesn't work.
singleton.ts
import { mockReset, mockDeep, DeepMockProxy } from "jest-mock-extended";
import { PrismaClient } from "#prisma/client";
import Prisma from "../src/db/prisma";
jest.mock("../src/db/prisma", () => {
return {
__esModule: true,
default: mockDeep<PrismaClient>(),
};
});
beforeEach(() => {
// eslint-disable-next-line no-use-before-define
mockReset(prismaMock);
});
export const prismaMock = Prisma as unknown as DeepMockProxy<PrismaClient>;
jest.config.ts
When turing off setupFilesAfterEnv option, testing *.test.ts files are working.
So I Want turn off setupFilesAfterEnv option in Integration testing
Is it applicable only when unit testing?
...
setupFilesAfterEnv: [
"./jest/singleton.ts"
]
I think your question is a bit incomplete, but I might know what you are talking about because I am running into a similar problem.
If you are trying to do the integration tests from prisma documentation, you need to unmock your prisma client on your integration tests. Otherwise it will still be mocked by your singleton.ts file
something like this:
myTest.test.js (integration test file)
jest.unmock("../src/db/prisma");
Another way of doing it, is just to remove the singleton from setupFilesAfterEnv and just import the prisma client from the singleton file inside your tests.
What I did:
I created 2 tests files (one for integration and another one for unit testing: CreateData.unit.test.ts and CreateData.int.test. I also created 2 singleton files:
singleton.unit.ts (I wanted that to be applied on my unit tests)
import { PrismaClient } from '#prisma/client';
import { mockDeep, mockReset, DeepMockProxy, mock } from 'jest-mock-extended';
import prismaClient from '../prismaClient';
jest.mock('../prismaClient', () => ({
__esModule: true,
default: mockDeep<PrismaClient>(),
}));
beforeEach(() => {
mockReset(prismaMock);
});
export const prismaMock = prismaClient as unknown as DeepMockProxy<PrismaClient>;
singleton.int.ts (I wanted that applied in my integration tests)
import prismaClient from '../prismaClient';
afterAll(async () => {
const deleteData = prismaClient.data.deleteMany();
await prismaClient.$transaction([
deleteData,
]);
await prismaClient.$disconnect();
});
export { prismaClient };
I removed setupFilesAfterEnv from jest.config.js
Then create your unit tests and integration tests. You don't need to unmock prisma client if you removed the singleton from setupFilesAfterEnv in jest.config.ts
myTest.unit.test.ts
import { prismaMock } from "<path>/singleton.unit";
import { CreateData } from "<path>/CreateData";
let createData;
let createDate = new Date();
const data = {
id: "randomId1234",
name: "Bob Singer",
email: "bob#gmail.com",
password: "123456",
};
beforeEach(() => {
createData = new CreateData();
});
describe('CreateData', () => {
it("should create new data", async () => {
const result = createData.execute(data);
prismaMock.data.create.mockResolvedValue(data);
await expect(result).resolves.toEqual({
id: "randomId1234",
name: "Bob Singer",
email: "bob#gmail.com",
password: "123456",
});
});
});
myTest.int.test.ts
import prismaClient from "<path>/singleton.int";
import { CreateData } from "<path>/CreateData"
let createData;
let createDate = new Date();
const data = {
id: "randomId1234",
name: "Bob Singer",
email: "bob#gmail.com",
password: "123456",
};
beforeEach(() => {
createData = new CreateData();
});
describe('CreateTrainer', () => {
it("should create new trainer", async () => {
const result = await createData.execute(data);
const newData = await prismaClient.data.findUnique({
where: {
email: "bob#gmail.com"
}
});
console.log(result);
expect(newData?.email).toEqual(data.email);
});
});

MongoError: pool is draining, new operations prohibited when using MongoMemoryServer in integration test

I'm using MongoMemoryServer to write an integration test. I have two integration test files. When I run the IT tests I see the following. I don't understand why. I'm using jestjs test framework.
I'm seeing the following error when I have two IT test files
MongoError: pool is draining, new operations prohibited
37 | for (const key in collections) {
38 | const collection = collections[key];
> 39 | await collection.deleteMany();
| ^
40 | }
41 | };
Here is my setup
//db-handler.js
const mongoose = require("mongoose");
const { MongoMemoryServer } = require("mongodb-memory-server");
const mongod = new MongoMemoryServer();
module.exports.connect = async () => {
const uri = await mongod.getConnectionString();
const mongooseOpts = {
useNewUrlParser: true,
autoReconnect: true,
reconnectTries: Number.MAX_VALUE,
reconnectInterval: 1000,
};
await mongoose.connect(uri, mongooseOpts);
};
module.exports.closeDatabase = async () => {
await mongoose.connection.dropDatabase();
await mongoose.connection.close();
await mongod.stop();
};
module.exports.clearDatabase = async () => {
const collections = mongoose.connection.collections;
for (const key in collections) {
const collection = collections[key];
await collection.deleteMany();
}
};
All my IT tests setup looks like this
//example.it.test.js
const supertest = require("supertest");
const dbHandler = require("./db-handler");
const app = require("../../src/app");
const request = supertest(app);
const SomeModel = require("../Some");
beforeAll(async () => await dbHandler.connect());
afterEach(async () => await dbHandler.clearDatabase());
afterAll(async () => await dbHandler.closeDatabase());
describe("Some Test Block", () => {
it("Test One", async (done) => {
await SomeModel.create({a: "a", b "b"});
const response = await request.get("/endPointToTest");
expect(response.status).toBe(200);
done();
});
When I have just a single IT test files, everything works fine. When I introduce a a new IT test file similar setup up as example.it.test.js, then the new test fails. The example error message above.
What am I missing? Does my setup need to change when I have multiple IT test files?
UPDATE ONE:
My package.json file looks like
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "jest --runInBand ./tests"
},
"devDependencies": {
"jest": "^25.2.7",
"mongodb-memory-server": "^6.5.2",
"nodemon": "^2.0.2",
"supertest": "^4.0.2"
},
"jest": {
"testEnvironment": "node",
"coveragePathIgnorePatterns": [
"/node_modules/"
]
}
}
I think the error message can be somewhat misleading for some cases.
It seems like the issue is related to "pool size being too small" or "jest runs tests in parallel", so the pool eventually ran out of usable pool.
However, if you look at what happens when mongo destroys the pool, you'll get the idea.
/**
* Destroy pool
* #method
*/
Pool.prototype.destroy = function(force, callback) {
...
// Set state to draining
stateTransition(this, DRAINING);
...
If you try to close the connection, the pool changes its internal state into 'draining' which causes the problem.
So what the error message is telling us is that there's still unfinished writing operation(s) going on after pool.destroy.
For my case, that was an active event listener callback runs every x seconds which does write to mongo.
I changed my test code to call event callback function directly instead of being called every x seconds.
That resolved my issue.
By default Jest runs tests in parallel with a “a worker pool of child processes that run tests” (Jest CLI docs). As per the Jest documentation.
Each test file is making a new connection to the mogodb server which is causing the error.
You could run the Jest test sequentially to avoid this issue.
The --runInBand or -i option of the Jest CLI allows you to run tests sequentially (in non-parallel mode).
This error (pool is draining, new operations prohibited) might occur if you are using the official mongodb library for node js or mongoose. Please specifying pool size while establishing mongodb connection.
module.exports.connect = async () => {
const uri = await mongod.getConnectionString();
const mongooseOpts = {
useNewUrlParser: true,
autoReconnect: true,
reconnectTries: Number.MAX_VALUE,
reconnectInterval: 1000,
poolSize: 10,
};
await mongoose.connect(uri, mongooseOpts);
};
I did the same way and faced into the same error.
This was resolved by removing by schema name in test only like I did below in some.test.js
** Rest all setup remains same **
//example.it.test.js
const dbHandler = require("./db-handler");
const SomeModel = require("../Some");
const SomeOtherModel = require("../SomeOther");
beforeAll(async () => await dbHandler.connect());
afterEach(async () => {
await SomeModel.remove({});
await SomeOtherModel.remove({});
});
afterAll(async () => await dbHandler.closeDatabase());
describe("Some Test Block", () => {
it("Test One", async (done) => {
await SomeModel.create({a: "a", SomeOther: 'SomeOther Data' });
const data = await SomeModel.getAll();
expect(data.length).toBe(1);
done();
});
});
For every It test case, it connects to the mongoose, and if any error occurred, mongoose does not disconnect.
Solution
You can put the it test case in the try catch block, but if possible use 2 point
Try to handle the error in the controllers and service, and so on
files if possible.
And whenever you write test case, there should be no error comes from the development code.
Don't write nested describe in a single describe, max 2 nested
describe we should use.
One temporary solution, I skip the all describe expect outmost where mongoose was connected.
One more temporary solution, disconnect the mongod and then see the status, then reconnect.

Jest - Mocking and testing the node.js filesystem

I have created a function which basically loops over an array and create files. I'm starting to get into testing using Jest to have some extra security in place to make sure everything works however I'm experiencing some issues trying to mock the Node.js filesystem.
This is the function I wish to test - function.ts:
export function generateFiles(root: string) {
fs.mkdirSync(path.join(root, '.vscode'));
files.forEach((file) => {
fs.writeFileSync(
path.join(root, file.path, file.name),
fs.readFileSync(path.join(__dirname, 'files', file.path, file.name), 'utf-8')
);
});
}
const files = [
{ name: 'tslint.json', path: '' },
{ name: 'tsconfig.json', path: '' },
{ name: 'extensions.json', path: '.vscode' },
];
I've been reading around but can't really figure out how to test this with jest. No examples to look at. I've tried to install mock-fs which should be a simple way of getting up and running with a mock version of the Node.js FS module but I honestly don't know where to start. This is my first attempt at making a simple test - which causes an error, says 'no such file or directory' - function.test.ts:
import fs from 'fs';
import mockfs from 'mock-fs';
beforeEach(() => {
mockfs({
'test.ts': '',
dir: {
'settings.json': 'yallo',
},
});
});
test('testing mock', () => {
const dir = fs.readdirSync('/dir');
expect(dir).toEqual(['dir']);;
});
afterAll(() => {
mockfs.restore();
});
Anyone who can point me in the right direction?
Since you want to test you implementation you can try this:
import fs from 'fs';
import generateFiles from 'function.ts';
// auto-mock fs
jest.mock('fs');
describe('generateFiles', () => {
beforeAll(() => {
// clear any previous calls
fs.writeFileSync.mockClear();
// since you're using fs.readFileSync
// set some retun data to be used in your implementation
fs.readFileSync.mockReturnValue('X')
// call your function
generateFiles('/root/test/path');
});
it('should match snapshot of calls', () => {
expect(fs.writeFileSync.mock.calls).toMatchSnapshot();
});
it('should have called 3 times', () => {
expect(fs.writeFileSync).toHaveBeenCalledTimes(3);
});
it('should have called with...', () => {
expect(fs.writeFileSync).toHaveBeenCalledWith(
'/root/test/path/tslint.json',
'X' // <- this is the mock return value from above
);
});
});
Here you can read more about the auto-mocking

How to test function in class using jest

I wasn't able to make unit testing worked using jest
I'm trying to test a specific function that's calling or expecting result from other function but I'm not sure why it is not working. I'm pretty new to unit testing and really have no idea how could I make it work. currently this is what I've tried
export class OrganizationService {
constructor() {
this.OrganizationRepo = new OrganizationRepository()
}
async getOrganizations(req) {
if (req.permission !== 'internal' && req.isAuthInternal === false) {
throw new Error('Unauthenticated')
}
const opt = { deleted: true }
return this.OrganizationRepo.listAll(opt)
}
}
This is my OrganizationRepository that extends the MongoDbRepo
import { MongoDbRepo } from './mongodb_repository'
export class OrganizationRepository extends MongoDbRepo {
constructor(collection = 'organizations') {
super(collection)
}
}
and this is the MongoDbRepo
const mongoClient = require('../config/mongo_db_connection')
const mongoDb = require('mongodb')
export class MongoDbRepo {
constructor(collectionName) {
this.collection = mongoClient.get().collection(collectionName)
}
listAll(opt) {
return new Promise((resolve, reject) => {
this.collection.find(opt).toArray((err, data) => {
if (err) {
reject(err)
}
resolve(data)
})
})
}
}
and this is the test that I've made
import { OrganizationService } from '../../../src/services/organization_service'
describe('getOrganizations', () => {
it('should return the list of organizations', () => {
// const OrgRepo = new OrganizationRepository()
const orgService = new OrganizationService()
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]")
// orgService.getOrganizations = jest.fn().mockReturnValue('')
const result = orgService.getOrganizations()
expect(result).toBe(OrgRepo)
})
})
I see two issues in the way you are testing:
1.
You are trying to test an asynchronous method, and on your test, you are not waiting for this method to be finished before your expect statement.
A good test structure should be:
it('should test your method', (done) => {
const orgService = new OrganizationService();
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]")
orgService.getOrganizations()
.then((result) => {
expect(result).toEqual(OrgRepo); // I recommend using "toEqual" when comparing arrays
done();
});
})
Don't forget to put done as a parameter for your test!
You can find more about testing asynchronous functions on the Jest official documentation.
2.
In order to test your method properly, you want to isolate it from external dependencies. Here, the actual method OrganizationRepo.listAll() is called. You want to mock this method, for instance with a spy, so that you control its result and only test the getOrganizations method. That would look like this:
it('should test your method', (done) => {
const req = {
// Whatever structure it needs to be sure that the error in your method is not thrown
};
const orgService = new OrganizationService();
const orgRepoMock = spyOn(orgService['OrganizationRepo'], 'listAll')
.and.returnValue(Promise.resolve("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]"));
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]");
orgService.getOrganizations(req)
.then((result) => {
expect(result).toEqual(OrgRepo); // I recommend using "toEqual" when comparing arrays
expect(orgRepoMock).toHaveBeenCalled(); // For good measure
done();
});
})
This way, we make sure that your method is isolated and its outcome cannot be altered by external methods.
For this particular method, I also recommend that you test the error throwing depending on the input of your method.
I was able to answer this, first is I mocked the repository using
jest.mock('path/to/repo')
const mockGetOne = jest.fn()
OrganizationRepository.protorype.getOne = mockGetOne
then the rest is the test

Dynamically Running Mocha Tests

I'm trying to run a series of tests dynamically. I have the following setup but it doesn't seem to run and I'm not getting any errors:
import Mocha from 'mocha';
const Test = Mocha.Test;
const Suite = Mocha.Suite;
const mocha = new Mocha();
for (let s in tests) {
let suite = Suite.create(mocha.suite, s);
tests[s].forEach((test) => {
console.log('add test', test.name)
suite.addTest(new Test(test.name), () => {
expect(1+1).to.equal(2);
});
});
}
mocha.run();
The tests I'm running look like this:
{ todo:
[ { name: 'POST /todos',
should: 'create a new todo',
method: 'POST',
endpoint: '/todos',
body: [Object] } ] }
(though at this point my test is just trying to check a basic expect)
Based on the console.logs the iteration seems fine and it appears to be adding the tests, so I'm confident in the flow of operations, I just can't get any execution or errors.
You have to pass the test function to the Test constructor, not to suite.addTest. So change your code to add your tests like this:
suite.addTest(new Test(test.name, () => {
expect(1+1).to.equal(2);
}));
Here is the entire code I'm running, adapted from your question:
import Mocha from 'mocha';
import { expect } from 'chai';
const Test = Mocha.Test;
const Suite = Mocha.Suite;
const mocha = new Mocha();
var tests = { todo:
[ { name: 'POST /todos',
should: 'create a new todo',
method: 'POST',
endpoint: '/todos',
body: [Object] } ] };
for (let s in tests) {
let suite = Suite.create(mocha.suite, s);
tests[s].forEach((test) => {
console.log('add test', test.name);
suite.addTest(new Test(test.name, () => {
expect(1+1).to.equal(2);
}));
});
}
mocha.run();
When I run the above with node_modules/.bin/babel-node test.es6, I get the output:
todo
✓ POST /todos
1 passing (5ms)
It's critical to test your test system and make sure it deals with passing and failing tests and thrown exceptions.
Since folks are counting on a build process to warn them about errors, you must also set the exit code to a non-zero if anything failed.
Below is a test script (which you must invoke with node test.js rather than mocha test.js) which exercises all paths through your test suite:
const Mocha = require('mocha')
const expect = require('chai').expect
var testRunner = new Mocha()
var testSuite = Mocha.Suite.create(testRunner.suite, 'Dynamic tests')
var tests = [ // Define some tasks to add to test suite.
{ name: 'POST /todos', f: () => true }, // Pass a test.
{ name: 'GET /nonos', f: () => false }, // Fail a test.
{ name: 'HEAD /hahas', f: () => { throw Error(0) } } // Throw an error.
]
tests.forEach(
test =>
// Create a test which value errors and caught exceptions.
testSuite.addTest(new Mocha.Test(test.name, function () {
expect(test.f()).to.be.true
}))
)
var suiteRun = testRunner.run() // Run the tests
process.on('exit', (code) => { // and set exit code.
process.exit(suiteRun.stats.failures > 0) // Non-zero exit indicates errors.
}) // Falling off end waits for Mocha events to finish.
Given that this is prominent in web searches for asynchronous mocha tests, I'll provide a couple more useful templates for folks to copy.
Embedded execution: The first directly adds tests which invoke an asynchronous faux-network call and check the result in a .then:
const Mocha = require('mocha')
const expect = require('chai').expect
var testRunner = new Mocha()
var testSuite = Mocha.Suite.create(testRunner.suite, 'Network tests')
var tests = [ // Define some long async tasks.
{ name: 'POST /todos', pass: true, wait: 3500, exception: null },
{ name: 'GET /nonos', pass: false, wait: 2500, exception: null },
{ name: 'HEAD /hahas', pass: true, wait: 1500, exception: 'no route to host' }
]
tests.forEach(
test =>
// Create a test which value errors and caught exceptions.
testSuite.addTest(new Mocha.Test(test.name, function () {
this.timeout(test.wait + 100) // so we can set waits above 2000ms
return asynchStuff(test).then(asyncResult => {
expect(asyncResult.pass).to.be.true
}) // No .catch() needed because Mocha.Test() handles them.
}))
)
var suiteRun = testRunner.run() // Run the tests
process.on('exit', (code) => { // and set exit code.
process.exit(suiteRun.stats.failures > 0) // Non-zero exit indicates errors.
}) // Falling off end waits for Mocha events to finish.
function asynchStuff (test) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
// console.log(test.name + ' on ' + test.endpoint + ': ' + test.wait + 'ms')
if (test.exception)
reject(Error(test.exception))
resolve({name: test.name, pass: test.pass}) // only need name and pass
}, test.wait)
})
}
This code handles passing and failing data, reports exceptions, and exits with a non-zero status if there were errors. The output reports all expected problems and additionally whines about the test taking a like time (3.5s):
Network tests
✓ POST /todos (3504ms)
1) GET /nonos
2) HEAD /hahas
1 passing (8s)
2 failing
1) Network tests GET /nonos:
AssertionError: expected false to be true
+ expected - actual
-false
+true
2) Network tests HEAD /hahas:
Error: no route to host
Delayed execution: This approach invokes all of the slow tasks before populating and starting the the mocha test suite:
const Mocha = require('mocha')
const expect = require('chai').expect
var testRunner = new Mocha()
var testSuite = Mocha.Suite.create(testRunner.suite, 'Network tests')
var tests = [ // Define some long async tasks.
{ name: 'POST /todos', pass: true, wait: 3500, exception: null },
{ name: 'GET /nonos', pass: false, wait: 2500, exception: null },
{ name: 'HEAD /hahas', pass: true, wait: 1500, exception: 'no route to host' }
]
Promise.all(tests.map( // Wait for all async operations to finish.
test => asynchStuff(test)
.catch(e => { // Resolve caught errors so Promise.all() finishes.
return {name: test.name, caughtError: e}
})
)).then(testList => // When all are done,
testList.map( // for each result,
asyncResult => // test value errors and exceptions.
testSuite.addTest(new Mocha.Test(asyncResult.name, function () {
if (asyncResult.caughtError) { // Check test object for caught errors
throw asyncResult.caughtError
}
expect(asyncResult.pass).to.be.true
}))
)
).then(x => { // When all tests are created,
var suiteRun = testRunner.run() // run the tests
process.on('exit', (code) => { // and set exit code.
process.exit(suiteRun.stats.failures > 0) // Non-zero exit indicates errors.
})
})
function asynchStuff (test) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
// console.log(test.name + ' on ' + test.endpoint + ': ' + test.wait + 'ms')
if (test.exception)
reject(Error(test.exception))
resolve({name: test.name, pass: test.pass}) // only need name and pass
}, test.wait)
})
}
The output is the same except that mocha doesn't whine about the slow test and instead believes the tests tool less than 10ms. The Promise.all waits for all the promises to resolve or reject then creates the tests to validate the results or report exceptions. This is a few lines longer than Embedded execution because it must:
Resolve exceptions so Promise.all() resolves.
Execute the tests in a final Promise.all().then()
Comments describing how folks pick which style to use could guide others. Share your wisdom!

Resources