mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 18:00:18 +01:00
Use fake provider for NetworkController unit tests (#18628)
* Use fake provider for NetworkController unit tests In the unit tests for NetworkController, it's important to prevent network requests from occurring. Currently we do that by using Nock. However, the `core` version of NetworkController uses a fake provider object. This is arguably a better approach for unit tests because it prevents us from having to think about the behavior that a specific middleware may have. For instance, the Infura middleware intercepts `eth_chainId` to return a static result, and the block cache middleware replaces the `latest` block tag with the latest block number, making an extra call to `eth_blockNumber` in doing so. We have to account for these kinds of behaviors when using Nock, but we do not need to do this when using a fake provider. This should make it easier to compare the difference between the unit tests in this repo vs. in the `core` repo, which should ultimately help us merge the two controllers together. * Rename fake-provider-engine to fake-provider * Rearrange imports * Move fake-provider and fake-block-tracker into a directory and exclude it from coverage * Make FakeBlockTracker inert, and fix JSDocs * Remove generics from FakeProvider * Call beforeCompleting (and beforeResolving) using async/await * Fix signature of sendAsync; align other signatures within FakeProvider * No need to check whether error is not a string * Don't exclude the provider-api-tests directory from coverage * Make sure to mock both net_version and eth_getBlockByNumber when testing network status * Fix FakeProvider so that none of the methods have optional callbacks
This commit is contained in:
parent
3776f4ad4c
commit
24eae1d3c6
File diff suppressed because it is too large
Load Diff
11
app/scripts/controllers/network/test/fake-block-tracker.ts
Normal file
11
app/scripts/controllers/network/test/fake-block-tracker.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { PollingBlockTracker } from 'eth-block-tracker';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acts like a PollingBlockTracker, but doesn't start the polling loop or
|
||||||
|
* make any requests.
|
||||||
|
*/
|
||||||
|
export class FakeBlockTracker extends PollingBlockTracker {
|
||||||
|
async _start() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
206
app/scripts/controllers/network/test/fake-provider.ts
Normal file
206
app/scripts/controllers/network/test/fake-provider.ts
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
import { inspect, isDeepStrictEqual } from 'util';
|
||||||
|
import {
|
||||||
|
JsonRpcEngine,
|
||||||
|
JsonRpcRequest,
|
||||||
|
JsonRpcResponse,
|
||||||
|
} from 'json-rpc-engine';
|
||||||
|
import { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider/dist/safe-event-emitter-provider';
|
||||||
|
|
||||||
|
// Store this in case it gets stubbed later
|
||||||
|
const originalSetTimeout = global.setTimeout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that allows specifying the behavior of a specific invocation of
|
||||||
|
* `sendAsync`. The `method` always identifies the stub, but the behavior
|
||||||
|
* may be specified multiple ways: `sendAsync` can either return a promise or
|
||||||
|
* throw an error, and if it returns a promise, that promise can either be
|
||||||
|
* resolved with a response object or reject with an error.
|
||||||
|
*
|
||||||
|
* @property request - Looks for a request matching these specifications.
|
||||||
|
* @property request.method - The RPC method to which this stub will be matched.
|
||||||
|
* @property request.params - The params to which this stub will be matched.
|
||||||
|
* @property response - Instructs `sendAsync` to return a promise that resolves
|
||||||
|
* with a response object.
|
||||||
|
* @property response.result - Specifies a successful response, with this as the
|
||||||
|
* `result`.
|
||||||
|
* @property response.error - Specifies an error response, with this as the
|
||||||
|
* `error`.
|
||||||
|
* @property error - Instructs `sendAsync` to return a promise that rejects with
|
||||||
|
* this error.
|
||||||
|
* @property implementation - Allows overriding `sendAsync` entirely. Useful if
|
||||||
|
* you want it to throw an error.
|
||||||
|
* @property delay - The amount of time that will pass after the callback is
|
||||||
|
* called with the response.
|
||||||
|
* @property discardAfterMatching - Usually after the stub matches a request, it
|
||||||
|
* is discarded, but setting this to true prevents that from happening. True by
|
||||||
|
* default.
|
||||||
|
* @property beforeCompleting - Sometimes it is useful to do something after the
|
||||||
|
* request is kicked off but before it ends (or, in terms of a `fetch` promise,
|
||||||
|
* when the promise is initiated but before it is resolved). You can pass an
|
||||||
|
* (async) function for this option to do this.
|
||||||
|
*/
|
||||||
|
export type FakeProviderStub = {
|
||||||
|
request: {
|
||||||
|
method: string;
|
||||||
|
params?: any[];
|
||||||
|
};
|
||||||
|
delay?: number;
|
||||||
|
discardAfterMatching?: boolean;
|
||||||
|
beforeCompleting?: () => void | Promise<void>;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
response: { result: any } | { error: string };
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
error: unknown;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
implementation: () => void;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of options that the FakeProviderEngine constructor takes.
|
||||||
|
*
|
||||||
|
* @property stubs - A set of objects that allow specifying the behavior
|
||||||
|
* of specific invocations of `sendAsync` matching a `method`.
|
||||||
|
*/
|
||||||
|
interface FakeProviderEngineOptions {
|
||||||
|
stubs?: FakeProviderStub[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FakeProviderEngine is an implementation of the provider that
|
||||||
|
* NetworkController exposes, which is actually an instance of
|
||||||
|
* Web3ProviderEngine (from the `web3-provider-engine` package). Hence it
|
||||||
|
* supports the same interface as Web3ProviderEngine, except that fake responses
|
||||||
|
* for any RPC methods that are accessed can be supplied via an API that is more
|
||||||
|
* succinct than using Jest's mocking API.
|
||||||
|
*/
|
||||||
|
// NOTE: We shouldn't need to extend from the "real" provider here, but
|
||||||
|
// we'd need a `SafeEventEmitterProvider` _interface_ and that doesn't exist (at
|
||||||
|
// least not yet).
|
||||||
|
export class FakeProvider extends SafeEventEmitterProvider {
|
||||||
|
calledStubs: FakeProviderStub[];
|
||||||
|
|
||||||
|
#originalStubs: FakeProviderStub[];
|
||||||
|
|
||||||
|
#stubs: FakeProviderStub[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a new instance of the fake provider.
|
||||||
|
*
|
||||||
|
* @param options - The options.
|
||||||
|
* @param options.stubs - A set of objects that allow specifying the behavior
|
||||||
|
* of specific invocations of `sendAsync` matching a `method`.
|
||||||
|
*/
|
||||||
|
constructor({ stubs = [] }: FakeProviderEngineOptions) {
|
||||||
|
super({ engine: new JsonRpcEngine() });
|
||||||
|
this.#originalStubs = stubs;
|
||||||
|
this.#stubs = this.#originalStubs.slice();
|
||||||
|
this.calledStubs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
send = (
|
||||||
|
payload: JsonRpcRequest<any>,
|
||||||
|
callback: (error: unknown, response?: JsonRpcResponse<any>) => void,
|
||||||
|
) => {
|
||||||
|
return this.#handleSend(payload, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
sendAsync = (
|
||||||
|
payload: JsonRpcRequest<any>,
|
||||||
|
callback: (error: unknown, response?: JsonRpcResponse<any>) => void,
|
||||||
|
) => {
|
||||||
|
return this.#handleSend(payload, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
#handleSend(
|
||||||
|
payload: JsonRpcRequest<any>,
|
||||||
|
callback: (error: unknown, response?: JsonRpcResponse<any>) => void,
|
||||||
|
) {
|
||||||
|
if (Array.isArray(payload)) {
|
||||||
|
throw new Error("Arrays aren't supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = this.#stubs.findIndex((stub) => {
|
||||||
|
return (
|
||||||
|
stub.request.method === payload.method &&
|
||||||
|
(!('params' in stub.request) ||
|
||||||
|
isDeepStrictEqual(stub.request.params, payload.params))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
const matchingCalledStubs = this.calledStubs.filter((stub) => {
|
||||||
|
return (
|
||||||
|
stub.request.method === payload.method &&
|
||||||
|
(!('params' in stub.request) ||
|
||||||
|
isDeepStrictEqual(stub.request.params, payload.params))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
let message = `Could not find any stubs matching: ${inspect(payload, {
|
||||||
|
depth: null,
|
||||||
|
})}`;
|
||||||
|
if (matchingCalledStubs.length > 0) {
|
||||||
|
message += `\n\nIt appears the following stubs were defined, but have been called already:\n\n${inspect(
|
||||||
|
matchingCalledStubs,
|
||||||
|
{ depth: null },
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(message);
|
||||||
|
} else {
|
||||||
|
const stub = this.#stubs[index];
|
||||||
|
|
||||||
|
if (stub.discardAfterMatching !== false) {
|
||||||
|
this.#stubs.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stub.delay) {
|
||||||
|
originalSetTimeout(() => {
|
||||||
|
this.#handleRequest(stub, callback);
|
||||||
|
}, stub.delay);
|
||||||
|
} else {
|
||||||
|
this.#handleRequest(stub, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.calledStubs.push({ ...stub });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #handleRequest(
|
||||||
|
stub: FakeProviderStub,
|
||||||
|
callback: (error: unknown, response?: JsonRpcResponse<any>) => void,
|
||||||
|
) {
|
||||||
|
if (stub.beforeCompleting) {
|
||||||
|
await stub.beforeCompleting();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('implementation' in stub) {
|
||||||
|
stub.implementation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('response' in stub) {
|
||||||
|
if ('result' in stub.response) {
|
||||||
|
callback(null, {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
id: 1,
|
||||||
|
result: stub.response.result,
|
||||||
|
});
|
||||||
|
} else if ('error' in stub.response) {
|
||||||
|
callback(null, {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
id: 1,
|
||||||
|
error: {
|
||||||
|
code: -999,
|
||||||
|
message: stub.response.error,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if ('error' in stub) {
|
||||||
|
callback(stub.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ module.exports = {
|
|||||||
'<rootDir>/app/scripts/constants/error-utils.js',
|
'<rootDir>/app/scripts/constants/error-utils.js',
|
||||||
'<rootDir>/app/scripts/controllers/network/**/*.js',
|
'<rootDir>/app/scripts/controllers/network/**/*.js',
|
||||||
'<rootDir>/app/scripts/controllers/network/**/*.ts',
|
'<rootDir>/app/scripts/controllers/network/**/*.ts',
|
||||||
|
'!<rootDir>/app/scripts/controllers/network/**/test/*.ts',
|
||||||
'<rootDir>/app/scripts/controllers/permissions/**/*.js',
|
'<rootDir>/app/scripts/controllers/permissions/**/*.js',
|
||||||
'<rootDir>/app/scripts/controllers/sign.ts',
|
'<rootDir>/app/scripts/controllers/sign.ts',
|
||||||
'<rootDir>/app/scripts/flask/**/*.js',
|
'<rootDir>/app/scripts/flask/**/*.js',
|
||||||
|
@ -408,6 +408,7 @@
|
|||||||
"@types/gulp-dart-sass": "^1.0.1",
|
"@types/gulp-dart-sass": "^1.0.1",
|
||||||
"@types/gulp-sourcemaps": "^0.0.35",
|
"@types/gulp-sourcemaps": "^0.0.35",
|
||||||
"@types/jest": "^29.1.2",
|
"@types/jest": "^29.1.2",
|
||||||
|
"@types/jest-when": "^3.5.2",
|
||||||
"@types/madge": "^5.0.0",
|
"@types/madge": "^5.0.0",
|
||||||
"@types/node": "^17.0.21",
|
"@types/node": "^17.0.21",
|
||||||
"@types/pify": "^5.0.1",
|
"@types/pify": "^5.0.1",
|
||||||
@ -483,6 +484,7 @@
|
|||||||
"jest": "^29.1.2",
|
"jest": "^29.1.2",
|
||||||
"jest-canvas-mock": "^2.3.1",
|
"jest-canvas-mock": "^2.3.1",
|
||||||
"jest-environment-jsdom": "^29.1.2",
|
"jest-environment-jsdom": "^29.1.2",
|
||||||
|
"jest-when": "^3.5.2",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsdom": "^11.2.0",
|
"jsdom": "^11.2.0",
|
||||||
"junit-report-merger": "^4.0.0",
|
"junit-report-merger": "^4.0.0",
|
||||||
|
20
yarn.lock
20
yarn.lock
@ -7416,6 +7416,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/jest-when@npm:^3.5.2":
|
||||||
|
version: 3.5.2
|
||||||
|
resolution: "@types/jest-when@npm:3.5.2"
|
||||||
|
dependencies:
|
||||||
|
"@types/jest": "*"
|
||||||
|
checksum: 106230dd71ee266bbd7620ab339a7305054e56ba7638f0eff9f222e67a959df0ad68a7f0108156c50f7005881bae59cd5c38b3760c101d060cdb3dac9cd77ee2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/jest@npm:*, @types/jest@npm:^29.1.2":
|
"@types/jest@npm:*, @types/jest@npm:^29.1.2":
|
||||||
version: 29.1.2
|
version: 29.1.2
|
||||||
resolution: "@types/jest@npm:29.1.2"
|
resolution: "@types/jest@npm:29.1.2"
|
||||||
@ -21886,6 +21895,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"jest-when@npm:^3.5.2":
|
||||||
|
version: 3.5.2
|
||||||
|
resolution: "jest-when@npm:3.5.2"
|
||||||
|
peerDependencies:
|
||||||
|
jest: ">= 25"
|
||||||
|
checksum: 9ad95552d377ef4d517c96a14c38bd8626d6856f518e7efc8a01d76a45a39d3c52281392c98da781c302114c78cd1ea17556c7f638d49d15971fcce9d58306e8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"jest-worker@npm:^26.5.0, jest-worker@npm:^26.6.2":
|
"jest-worker@npm:^26.5.0, jest-worker@npm:^26.6.2":
|
||||||
version: 26.6.2
|
version: 26.6.2
|
||||||
resolution: "jest-worker@npm:26.6.2"
|
resolution: "jest-worker@npm:26.6.2"
|
||||||
@ -24218,6 +24236,7 @@ __metadata:
|
|||||||
"@types/gulp-dart-sass": ^1.0.1
|
"@types/gulp-dart-sass": ^1.0.1
|
||||||
"@types/gulp-sourcemaps": ^0.0.35
|
"@types/gulp-sourcemaps": ^0.0.35
|
||||||
"@types/jest": ^29.1.2
|
"@types/jest": ^29.1.2
|
||||||
|
"@types/jest-when": ^3.5.2
|
||||||
"@types/madge": ^5.0.0
|
"@types/madge": ^5.0.0
|
||||||
"@types/node": ^17.0.21
|
"@types/node": ^17.0.21
|
||||||
"@types/pify": ^5.0.1
|
"@types/pify": ^5.0.1
|
||||||
@ -24331,6 +24350,7 @@ __metadata:
|
|||||||
jest-canvas-mock: ^2.3.1
|
jest-canvas-mock: ^2.3.1
|
||||||
jest-environment-jsdom: ^29.1.2
|
jest-environment-jsdom: ^29.1.2
|
||||||
jest-junit: ^14.0.1
|
jest-junit: ^14.0.1
|
||||||
|
jest-when: ^3.5.2
|
||||||
js-yaml: ^4.1.0
|
js-yaml: ^4.1.0
|
||||||
jsdom: ^11.2.0
|
jsdom: ^11.2.0
|
||||||
json-rpc-engine: ^6.1.0
|
json-rpc-engine: ^6.1.0
|
||||||
|
Loading…
Reference in New Issue
Block a user