testjavascript / nodejs-integration-tests-best-practices Goto Github PK
View Code? Open in Web Editor NEW✅ Beyond the basics of Node.js testing. Including a super-comprehensive best practices list and an example app (March 2024)
✅ Beyond the basics of Node.js testing. Including a super-comprehensive best practices list and an example app (March 2024)
basic-tests, "order failed, send mail to admin",
Is failing for me with: NetConnectNotAllowedError: Nock: Disallowed net connect for "mailer.com:443/send"
possible fix:
Here is a recommended eslint config:
{
"plugins": ["@typescript-eslint/eslint-plugin", "jest"],
"extends": [
"plugin:security/recommended",
"plugin:promise/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:import/typescript",
"prettier",
"prettier/@typescript-eslint"
],
"root": true,
"env": {
"node": true,
"jest": true
},
"rules": {
"jest/no-disabled-tests": "error",
"jest/no-focused-tests": "error",
"jest/no-identical-title": "error",
"jest/prefer-to-have-length": "warn",
"jest/valid-title": "error",
"jest/valid-expect": "error",
"jest/no-if": "error",
"jest/require-top-level-describe": "error",
"jest/no-test-prefixes": "error",
"jest/prefer-todo": "warn",
"jest/expect-expect": "error",
"jest/no-deprecated-functions": "error"
}
}
Currently the example app shows only /POST tests, we should add /GET and in overall ensure we have tests for the basic integration KATA:
For each scenario, one should test at least the following:
I picked this topic as my first challenge. Before I code anything, I'd like to hear your thoughts 🔥
The challenge:
Solutions (not all of them are good, just stating the options):
this line seems to be redundant as the server is initialized twice for each test.
@jhenaoz @Thormod @goldbergyoni
I want to focus on MySQL and Postgres, which are the most popular databases engines in those days.
We can add more databases later (MongoDB is a good candidate IMO)
Also, Because of WSL2, I think we can recommend using Linux practices for Windows, WDYT?
The challenge:
Optional Solutions
Postgres:
Tasks
This is about simulating 3rd party behaviour, the users service, using nock @mikicho
Example here:
#43
YO, Two strategic topics:
Repo name and concept - Seems like we're going with the repo name: 'Integration Tests Best Practices', Any counter thoughts? Maybe 'Node.js Tests Best Practices' which makes it even more generic, opens the door for more topics but also less focused?
Best practices sketch - I'm including here a first draft of best practices idea and categorization (there will be more and each will include a longer explanation with code example), does this feel like the right taxonomy and will end in interesting content?
Super simple, declarative and short (7 loc) tests
More will come here. Suggest more?
I'm excited to kick-off this project which I find to be super-important. We all know how tricky is perfecting the environment and the integration tests themselves. This can be a great source for many and our playground to master this technique. We will receive feedback and validation from the community and there is no better way for self-improvement.
I suggest the following workflow:
List of features
Approaching URL (with Axios for example) and not relating to 'app' object with supertest is better because:
Create a recipe with the same code as the example-ap, only with TypeScript, so both the SUT and the test code are typed.
Before picking this issue, it's recommended to have a short tech planning chat here and only then upon sync to get up to speed.
The example app DB schema is too simplistic and might not tell the truth when conducting performance benchmarks. It could be great to add a few more fields to the main table and also one more relation.
Before picking this issue, it's recommended to have a short tech planning chat here and only then upon sync to get up to speed.
Intergation tests are powerful becuase they allow testing the dark parts of the engine. Migrations are one of the darkest corners.
Two proposed tests:
We can test this with:
test('When older Order record exist with empty hasSupportTickets and trying to delete it, then deletion fails with HTTP 409', () => {
// Arrange
migrate the DB to v0.1
insert an order with empty hasSupportTickets
migrate the DB to v0.2
// Act
try to delete the order
// Assert
...
}
We have a user which has a few comments.
When we delete the user, we should delete all its comments as well.
How can we test that the comments get deleted?
We can't do this through an API because the user doesn't exist anymore and:
GET /api/users/3/comments
Will return 404
error code because user 3
doesn't exist and not because user has 0 comments.
Do we want to demonstrate how to enable requests to a specific port on localhost with nock?
nock.disableNetConnect()
nock.enableNetConnect('127.0.0.1:43050')
Maybe this is too nock
specific and had nothing to do about testing
I'm new to nock.
Is it common to create a set of nocks in the global setup and override them if possible on the local tests?
For example for URLs that get called in a lot of requests (authorization, permissions, sqs...)
Some tests are grey-box tests (e.g. they stub some code object) and can only get executed against the local env. Otheres are pure black-box, they approach the API and don't assume that the api-under-test is within the same process - These tests can get executed against remote environment like staging. To achieve this, one should tag those tests and then grep for them during execution
Since we deal with realistic tests with DB, its sometime too wasteful to run the tests in watch-mode. Jest suspend allows the developers to put the watch-mode in pause:
https://github.com/unional/jest-watch-suspend
Before picking this issue, it's recommended to have a short tech planning chat here and only then upon sync to get up to speed.
I have a suggestion to defer the weekenthon in 3 weeks, here's why:
I would propose Friday January 7th or 14th + Sunday 9th or 16th, each meeting 2 hours, gonna be fun and insightful.
WDYT?
My 2 cent - Stub has better ROI than fake (live MQ), Sebas makes the call
It's too easy for integration test to step on each other toes by adding identical records when there is a unique constraint. For example, test 11 tries to add the user {name: 'messi'}, but also test 47 adds the same user. One of them will fail because 'name' is unique.
Solution 1, what I used by now - Each test add a timestamp:
const userToAdd = {name:
messi ${Math.random()})
Solution 2, use a stateful factory - The test just calls some factory that manages the state and always provides fresh records:
dataFactory.getUser('messi')//returns 'messi-55', or any unique number
A better approach might be to use some factory lib like rosie:
https://www.npmjs.com/package/rosie
Which one is better?
[Based on comments from @mikicho]
Just checking out that we're going by the plan and meet this weekend to being this repo close to release?
@goldbergyoni
I'm talking about this file: example-application/test/basic-tests.js
By mistake?
Clone the example app, only use Mocha instead of Jest
Before picking this issue, it's recommended to have a short tech planning chat here and only then upon sync to get up to speed.
Dear collaborator, This is just a catch-up summary to help busy people follow and chime-in:
I'm working on the error handing tests recipe, what do you think that this should cover? My thoughts below
We assume that the app has its own CustomError which can tell whether the error is catastrophic (process should exit) and a dedicated ErrorHandler object
Configure GitHub actions for this repo so anytime we PR/push, all the test will run. Since most of the tests include a DB, which should take care to configure the CI with docker-compose.
Before picking this issue, it's recommended to have a short tech planning chat here and only then upon sync to get up to speed.
nvm use and more
#35 (comment)
Hey,
Some thoughts regarding cleaning after nock (and in general),
some tests have:
nock.disableNetConnect();
nock.enableNetConnect('127.0.0.1');
Inside the beforeAll hook, but no proper clean up is never done:
nock.enableNetConnect();
We also use nock(...).persist() on a few occasions and never clean them up so they persist between different tests. A good nock clean up would be:
nock.cleanAll();
nock.enableNetConnect();
Maybe add a new rule such as: “Each test should clean after itself”? (same goes for sinon and all). If we use nock inside the beforeAll hook, we need to clean inside afterAll, same for beforeEach and afterEach.
Therefore, we shouldn’t have calls like this:
beforeEach(() => {
nock.cleanAll();
Because we assume if nock was used in another test it was already cleaned up by that test.
Another small thing, we use async (done) inside most of the before/after hooks, isn't redundant to use both async and done in the same time?
Golden rule - A test visitor will always find the failure reason within the test or at the worst case in the nearest hook.
Practically:
Makes sense?
Originally posted by @goldbergyoni in #19 (comment)
With component test, we thrive to isolate our component from the outside world.
we can do this easily with nock:
nock.disableNetConnect();
nock.enableNetConnect('127.0.0.1')
We could show even better how to simulate the real-world chaos in a lab by showing the following tests:
// ❌ Anti-Pattern: We didn't test the scenario where the mailer does not reply (timeout)
// ❌ Anti-Pattern: We didn't test the scenario where the mailer reply slowly (delay)
// ❌ Anti-Pattern: We didn't test the scenario of occasional one-time response failure which can be mitigated with a retry
This doesn't include the entire DB interaction rather just the docker-compose config of DB to be as performant as possible. If we include benchmarks graph - It will be amazing
We already have one for PG, the next two that are needed: Mongo, MySQL
There's no need to change code, just share an example docker-compose with optimizations for testing
See PG recipe here:
#86
Use function with validators instead of object with regex
scope.isDone()
with body match (nocks stype) or get the body from the nocks interceptor and expect
As we arrange some tests with:
process.env.SEND_MAILS = "true";
We need to clean this value on end but we cannot simply set it to “false” as it might actually originally be “true”.
Instead we might restore the environment back to its initial state after each run:
BeforeAll(() => { defaultEnv = process.env …
AfterEach(() => { process.env = defaultEnv ...
On the other hand, let’s assume my local env.SEND_MAILS is set to “false”. When I block all external calls in a test - it will pass on my machine. But if the same test is run on another machine, in which env.SEND_MAILS is set to “true”, it will fail.
Should each test declare a SEND_MAILS value in its setup? in contrast to the previous point.
What should be considered as a good practice here? Maybe tests should be aware whether they test a predefined behaviour, in which we set SEND_MAILS in the beginning. Others should test a concrete deployment behaviour - in which we do not change the environment, and if we do, we use the first practice and restore to default.
Your opinion?
Here we use sinon.stub in beforeAll. This way we provide only one stub for the whole suite, which cannot be restored later.
Should we always stub inside beforeEach or the arrange phase but not in beforeAll?
Trying to focus on releasing the great materials that we have here. How about the following simple plan:
This is just an optional idea, don't feel obliged to opt in. Just let me know whether this works for you.
@goldbergyoni @Thormod @jhenaoz
we talked before that we need to use the API as much as possible.
For example, we can do:
it('should get user by id', () => {
// Arrange
const newUser = httpClient.post('/users', userDetails);
// Act
const user = httpClient.get(`users/${newUser.id}`);
// Assert
...
});
My concern with this approach that we implicitly testing POST /users
endpoint and this might fail the test, as a result, plenty of fails will fail as well and the failure reason won't be clear.
A possible solution (not sure if I'm comfortable with this) is to talk with DB directly in the Arrange
step.
WDYT?
Our tests currently use sinon.createSandbox to isolate test doubles between the tests. However, starting from a recent version, sinon is by default sandboxed so we can use is as-is and remove the sandbox usage.
Before picking this issue, it's recommended to have a short tech planning chat here and only then upon sync to get up to speed.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.