-
Notifications
You must be signed in to change notification settings - Fork 17
refactor(test): centralize integration test mocks #421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Important Review skippedMore than 25% of the files skipped due to max files limit. The review is being skipped to prevent a low-quality review. 41 files out of 148 files are above the max files limit of 100. Please upgrade to Pro plan to get higher limits. You can disable this status message by setting the ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🤖 My Senior Dev150 files reviewed • 3 high risk • 50 need attention 🚨 High Risk:
💬 Try: 📖 All commands & personasYou can interact with me by mentioning In PR comments or on any line of code:
Slash commands:
AI Personas (mention to get their perspective):
For the best experience, view this PR on myseniordev.com — includes AI chat, file annotations, and interactive reviews. |
f97f346 to
fc098c1
Compare
| if (process.env.MIGRATE_MOCKS) { | ||
| const legacyLoader = new LegacyMockLoader(dirname, name); | ||
| return new RecordingMockLoader(legacyLoader, unifiedMockPath); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure I follow why a runtime migration is required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just a dev utility to help migrate old tests to the new format. Added a comment to clarify
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the migration idempotent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Migration itself is not idempotent, but restoring from an unsuccessful migration is as simple as deleting the new mock file (as we do not delete the old mocks, an the migration guide will state that a clean, successful run, should be tested before deleting those)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it should be idempotent as well, as we record everything the test require and create the new file once that is all done, so unless saving the file fails mid-write (could happen, but it would be extremely rare I think), the migration is idempotent
| } catch (error) { | ||
| if (throwOnMissing) { | ||
| throw new Error(`Failed to load mock data from ${filePath}: ${error.message} ${identity ? JSON.stringify(identity, null, 2) : ''}`); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do we return when throwOnMissing is false?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. It now explicitly returns undefined when throwOnMissing is false, matching the new provider's behavior
|
|
||
| if (!apiMock) { | ||
| apiMock = this.mockData.api[method.toUpperCase()]?.[`/${normalizedEndpoint}`]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the double attempt? are we not controlling the endpoint format
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It handles endpoints stored with or without leading slashes (legacy vs new recordings). When testing migrating multiple of the existing tests this was a recurring issue. It seems like we don't have it standarized. Added a comment to clarify.
| } | ||
| } | ||
| } | ||
| } catch (e) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you explain what the stack trace parsing is for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It auto-detects the test filename (e.g., foo.test.ts) to find the matching JSON mock (foo.test.json). Added a comment
vitest.setup.ts
Outdated
| let unifiedFileExists = false; | ||
| try { | ||
| // @ts-ignore | ||
| const fsSync = require('fs'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doing sync IO in what looks like a constructor is surprising. any reason to not make getMockLoader async?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored getFixtureProvider to be async. The constructor now kicks off the promise, and methods await it. Originally I kept the whole constructor synchronized so there was no signature change. Initially I had an asycn factory method, but I was worried the sync constructor would be used. Kicking off the promise at the constructor and making the already async methods wait for that promise was something I hadn't thought at the time
|
|
||
| async getBatchSaveData(modelName: string) { | ||
| await this.loadMockFile(); | ||
| return this.mockData.nango?.batchSave?.[modelName]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we ok returning undefined if data doesn't exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Updated to throw errors for missing critical data (like batchSave), matching the legacy behavior
vitest.setup.ts
Outdated
|
|
||
| constructor(private mockFilePath: string) {} | ||
|
|
||
| private async loadMockFile() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does it bring anything to load the data dynamically instead of when instantiating the loader?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, it was overcomplicated. Refactored to load data once in the constructor
vitest.setup.ts
Outdated
| import get from 'lodash-es/get.js'; | ||
| import type { Pagination, CursorPagination, LinkPagination, OffsetPagination, OffsetCalculationMethod } from '@nangohq/types'; | ||
|
|
||
| interface MockLoaderStrategy { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about FixtureProvider and then LegacyFixtureProvider, ....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, much better
f6d8df4 to
9104300
Compare
| public async getBatchSaveData(modelName: string) { | ||
| const data = await this.getMockFile(`${this.name}/${modelName}/batchSave`, true); | ||
| return data; | ||
| return (await this.fixtureProvider).getBatchSaveData(modelName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not a huge fan of awaiting the same promise but for a utility feature, why not. This or implementing some sort of Factory function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Originally I had a factory function, but it would force migration to that factory method. I followed the same reasoning, this a utility so it doesn't hurt that much.
- Make fixture loading async to avoid sync I/O - Enforce strict error handling for missing mock data - Rename strategies to FixtureProviders - Add types for mock data structures
9104300 to
c4f2759
Compare
Describe your changes
Refactoring of how integration test mocks are managed. We are moving from a scattered, directory-based mock structure to a centralized, single-file approach using
.test.jsonfiles co-located with their corresponding tests.Key Changes:
*.test.jsonfile now holds all mock data (input, output, API calls, etc.) for a single test, making tests easier to read and maintain.NangoActionMockto use a strategy pattern for loading mocks (now called "Fixture Providers"), ensuring full backward compatibility while enabling the new system.LegacyFixtureProvider: For old tests.UnifiedFixtureProvider: For new, centralized.test.jsonfiles.RecordingFixtureProvider: A new utility to automatically migrate old tests to the new format.getFixtureProviderreturning a promise.batchSaveoroutput), matching the behavior of the legacy system..test.jsonfiles by running existing tests with aMIGRATE_MOCKS=trueenvironment variable.This new structure improves the developer experience by making test data easier to manage and reason about, and it provides a simple, automated path for migrating existing tests.
Issue ticket number and link
NAN-4463
Checklist before requesting a review (skip if just adding/editing APIs & templates)
retriesnango.paginatecall is used instead of awhile (true)loopnango.yamlhasauto_start: falsefullsync thentrack_deletes: trueis setMigration Guide
We have made migration as automated as possible. Follow these steps to migrate an integration to the new structure.
Step 1: Ensure Tests Pass
Before starting, make sure the existing tests for the integration are passing using the legacy mocks.
Step 2: Run with Migration Mode
Run the tests again with the
MIGRATE_MOCKSenvironment variable set totrue. This will trigger theRecordingFixtureProvider, which will execute the tests against the old mocks and generate the new.test.jsonfiles.Note: You will see new
*.test.jsonfiles appear next to your*.test.tsfiles.Step 3: Verify and Clean Up
.test.jsonfiles and use theUnifiedFixtureProvider.mocks/directory.Step 4: Troubleshooting
If a test fails after migration:
.test.jsonand verify the data looks correct.apisection. The loader uses subset matching, so ensure the critical parameters in therequestobject match what the code is sending..test.jsonand re-run Step 2 to re-record the interaction.Step 5: Commit
Commit the new
.test.jsonfiles and the removal of the oldmocks/directories.