Merge pull request #2102 from github/koesie10/mock-objects

Add new `mockedObject` function
This commit is contained in:
Koen Vlaswinkel
2023-02-24 15:43:19 +01:00
committed by GitHub
9 changed files with 85 additions and 50 deletions

View File

@@ -105,7 +105,7 @@ export function transformBqrsResultSet(
};
}
type BqrsKind =
export type BqrsKind =
| "String"
| "Float"
| "Integer"

View File

@@ -4,13 +4,13 @@ import { join } from "path";
import { CancellationToken, ExtensionContext, Uri, workspace } from "vscode";
import {
DatabaseEventKind,
DatabaseManager,
DatabaseItemImpl,
DatabaseContents,
FullDatabaseOptions,
findSourceArchive,
DatabaseEventKind,
DatabaseItemImpl,
DatabaseManager,
DatabaseResolver,
findSourceArchive,
FullDatabaseOptions,
} from "../../../src/local-databases";
import { Logger } from "../../../src/common";
import { ProgressCallback } from "../../../src/commandRunner";
@@ -24,6 +24,7 @@ import { QueryRunner } from "../../../src/queryRunner";
import * as helpers from "../../../src/helpers";
import { Setting } from "../../../src/config";
import { QlPackGenerator } from "../../../src/qlpack-generator";
import { mockedObject } from "../utils/mocking.helpers";
describe("local databases", () => {
const MOCK_DB_OPTIONS: FullDatabaseOptions = {
@@ -77,20 +78,20 @@ describe("local databases", () => {
databaseManager = new DatabaseManager(
extensionContext,
{
mockedObject<QueryRunner>({
registerDatabase: registerSpy,
deregisterDatabase: deregisterSpy,
onStart: () => {
/**/
},
} as unknown as QueryRunner,
{
}),
mockedObject<CodeQLCliServer>({
resolveDatabase: resolveDatabaseSpy,
packAdd: packAddSpy,
} as unknown as CodeQLCliServer,
{
}),
mockedObject<Logger>({
log: logSpy,
} as unknown as Logger,
}),
);
// Unfortunately, during a test it is not possible to convert from

View File

@@ -6,6 +6,7 @@ import { CodeQLCliServer } from "../../../src/cli";
import { Uri, workspace } from "vscode";
import { getErrorMessage } from "../../../src/pure/helpers-pure";
import * as tmp from "tmp";
import { mockedObject } from "../utils/mocking.helpers";
describe("QlPackGenerator", () => {
let packFolderName: string;
@@ -14,7 +15,7 @@ describe("QlPackGenerator", () => {
let exampleQlFilePath: string;
let language: string;
let generator: QlPackGenerator;
let packAddSpy: jest.SpyInstance;
let packAddSpy: jest.Mock<any, []>;
let dir: tmp.DirResult;
beforeEach(async () => {
@@ -28,9 +29,9 @@ describe("QlPackGenerator", () => {
exampleQlFilePath = join(packFolderPath, "example.ql");
packAddSpy = jest.fn();
const mockCli = {
const mockCli = mockedObject<CodeQLCliServer>({
packAdd: packAddSpy,
} as unknown as CodeQLCliServer;
});
generator = new QlPackGenerator(
packFolderName,

View File

@@ -5,6 +5,7 @@ import { CodeQLCliServer } from "../../../../src/cli";
import { DatabaseItem } from "../../../../src/local-databases";
import { Uri } from "vscode";
import { QueryWithResults } from "../../../../src/run-queries-shared";
import { mockedObject } from "../../utils/mocking.helpers";
/**
*
@@ -32,7 +33,7 @@ describe("AstBuilder", () => {
let overrides: Record<string, Record<string, unknown> | undefined>;
beforeEach(() => {
mockCli = {
mockCli = mockedObject<CodeQLCliServer>({
bqrsDecode: jest
.fn()
.mockImplementation(
@@ -40,7 +41,7 @@ describe("AstBuilder", () => {
return mockDecode(resultSet);
},
),
} as unknown as CodeQLCliServer;
});
overrides = {
nodes: undefined,
edges: undefined,

View File

@@ -11,6 +11,7 @@ import {
} from "../../../../src/contextual/queryResolver";
import { CodeQLCliServer } from "../../../../src/cli";
import { DatabaseItem } from "../../../../src/local-databases";
import { mockedObject } from "../../utils/mocking.helpers";
describe("queryResolver", () => {
let getQlPackForDbschemeSpy: jest.SpiedFunction<
@@ -20,9 +21,11 @@ describe("queryResolver", () => {
typeof helpers.getPrimaryDbscheme
>;
const mockCli = {
resolveQueriesInSuite: jest.fn(),
};
const resolveQueriesInSuite = jest.fn();
const mockCli = mockedObject<CodeQLCliServer>({
resolveQueriesInSuite,
});
beforeEach(() => {
getQlPackForDbschemeSpy = jest
@@ -41,20 +44,20 @@ describe("queryResolver", () => {
describe("resolveQueries", () => {
it("should resolve a query", async () => {
mockCli.resolveQueriesInSuite.mockReturnValue(["a", "b"]);
resolveQueriesInSuite.mockReturnValue(["a", "b"]);
const result = await resolveQueries(
mockCli as unknown as CodeQLCliServer,
mockCli,
{ dbschemePack: "my-qlpack", dbschemePackIsLibraryPack: false },
KeyType.DefinitionQuery,
);
expect(result).toEqual(["a", "b"]);
expect(mockCli.resolveQueriesInSuite).toHaveBeenCalledWith(
expect(resolveQueriesInSuite).toHaveBeenCalledWith(
expect.stringMatching(/\.qls$/),
[],
);
const fileName = mockCli.resolveQueriesInSuite.mock.calls[0][0];
const fileName = resolveQueriesInSuite.mock.calls[0][0];
expect(load(await fs.readFile(fileName, "utf-8"))).toEqual([
{
@@ -69,11 +72,11 @@ describe("queryResolver", () => {
});
it("should throw an error when there are no queries found", async () => {
mockCli.resolveQueriesInSuite.mockReturnValue([]);
resolveQueriesInSuite.mockReturnValue([]);
try {
await resolveQueries(
mockCli as unknown as CodeQLCliServer,
mockCli,
{ dbschemePack: "my-qlpack", dbschemePackIsLibraryPack: false },
KeyType.DefinitionQuery,
);
@@ -100,10 +103,7 @@ describe("queryResolver", () => {
},
},
} as unknown as DatabaseItem;
const result = await qlpackOfDatabase(
mockCli as unknown as CodeQLCliServer,
db,
);
const result = await qlpackOfDatabase(mockCli, db);
expect(result).toEqual({
dbschemePack: "my-qlpack",
dbschemePackIsLibraryPack: false,

View File

@@ -30,6 +30,7 @@ import {
QueryResultType,
} from "../../../src/pure/legacy-messages";
import { sleep } from "../../../src/pure/time";
import { mockedObject } from "../utils/mocking.helpers";
describe("query-results", () => {
let queryPath: string;
@@ -139,9 +140,9 @@ describe("query-results", () => {
const completedQuery = fqi.completedQuery!;
const spy = jest.fn();
const mockServer = {
const mockServer = mockedObject<CodeQLCliServer>({
sortBqrs: spy,
} as unknown as CodeQLCliServer;
});
const sortState = {
columnIndex: 1,
sortDirection: SortDirection.desc,
@@ -196,9 +197,9 @@ describe("query-results", () => {
await ensureDir(basename(interpretedResultsPath));
mockServer = {
mockServer = mockedObject<CodeQLCliServer>({
interpretBqrsSarif: spy,
} as unknown as CodeQLCliServer;
});
});
afterEach(async () => {

View File

@@ -16,6 +16,8 @@ import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder";
import { QueryInProgress } from "../../../src/legacy-query-server/run-queries";
import { LegacyQueryRunner } from "../../../src/legacy-query-server/legacyRunner";
import { DatabaseItem } from "../../../src/local-databases";
import { DeepPartial, mockedObject } from "../utils/mocking.helpers";
import { BqrsKind } from "../../../src/pure/bqrs-cli-types";
describe("run-queries", () => {
let isCanarySpy: jest.SpiedFunction<typeof config.isCanary>;
@@ -77,7 +79,7 @@ describe("run-queries", () => {
],
bqrsDecode: [
{
columns: [{ kind: "NotString" }, { kind: "String" }],
columns: [{ kind: "NotString" as BqrsKind }, { kind: "String" }],
tuples: [
["a", "b"],
["c", "d"],
@@ -89,8 +91,8 @@ describe("run-queries", () => {
// this won't happen with the real CLI, but it's a good test
columns: [
{ kind: "String" },
{ kind: "NotString" },
{ kind: "StillNotString" },
{ kind: "NotString" as BqrsKind },
{ kind: "StillNotString" as BqrsKind },
],
tuples: [["a", "b", "c"]],
},
@@ -125,7 +127,7 @@ describe("run-queries", () => {
],
bqrsDecode: [
{
columns: [{ kind: "NotString" }, { kind: "String" }],
columns: [{ kind: "NotString" as BqrsKind }, { kind: "String" }],
// We only escape string columns. In practice, we will only see quotes in strings, but
// it is a good test anyway.
tuples: [
@@ -312,7 +314,7 @@ describe("run-queries", () => {
function createMockQueryServerClient(
cliServer?: CodeQLCliServer,
): QueryServerClient {
return {
return mockedObject<QueryServerClient>({
config: {
timeoutSecs: 5,
},
@@ -326,20 +328,32 @@ describe("run-queries", () => {
log: jest.fn(),
},
cliServer,
} as unknown as QueryServerClient;
});
}
// A type that represents the mocked methods of a CodeQLCliServer. Exclude any non-methods.
// This allows passing in an array of return values for a single method.
type MockedCLIMethods = {
[K in keyof CodeQLCliServer]: CodeQLCliServer[K] extends (
...args: any
) => any
? Array<DeepPartial<Awaited<ReturnType<CodeQLCliServer[K]>>>>
: never;
};
function createMockCliServer(
mockOperations: Record<string, any[]>,
mockOperations: Partial<MockedCLIMethods>,
): CodeQLCliServer {
const mockServer: Record<string, any> = {};
const mockedMethods: Record<string, jest.Mock> = {};
for (const [operation, returns] of Object.entries(mockOperations)) {
mockServer[operation] = jest.fn();
returns.forEach((returnValue) => {
mockServer[operation].mockResolvedValueOnce(returnValue);
const fn = jest.fn();
returns.forEach((returnValue: any) => {
fn.mockResolvedValueOnce(returnValue);
});
mockedMethods[operation] = fn;
}
return mockServer as unknown as CodeQLCliServer;
return mockedObject<CodeQLCliServer>(mockedMethods);
}
});

View File

@@ -9,6 +9,7 @@ import {
DatabaseManager,
FullDatabaseOptions,
} from "../../../src/local-databases";
import { mockedObject } from "../utils/mocking.helpers";
jest.mock("fs-extra", () => {
const original = jest.requireActual("fs-extra");
@@ -74,15 +75,15 @@ describe("test-adapter", () => {
jest.spyOn(preTestDatabaseItem, "isAffectedByTest").mockResolvedValue(true);
adapter = new QLTestAdapter(
{
mockedObject<WorkspaceFolder>({
name: "ABC",
uri: Uri.parse("file:/ab/c"),
} as WorkspaceFolder,
{
}),
mockedObject<CodeQLCliServer>({
runTests: runTestsSpy,
resolveQlpacks: resolveQlpacksSpy,
resolveTests: resolveTestsSpy,
} as unknown as CodeQLCliServer,
}),
fakeDatabaseManager,
);
});

View File

@@ -0,0 +1,16 @@
export type DeepPartial<T> = T extends object
? {
[P in keyof T]?: DeepPartial<T[P]>;
}
: T;
export function mockedObject<T extends object>(props: DeepPartial<T>): T {
return new Proxy<T>({} as unknown as T, {
get: (_target, prop) => {
if (prop in props) {
return (props as any)[prop];
}
throw new Error(`Method ${String(prop)} not mocked`);
},
});
}