Skip to content

Commit 99039bb

Browse files
authored
chore: refactor integration test setup (#79)
1 parent 4c92c52 commit 99039bb

File tree

7 files changed

+120
-128
lines changed

7 files changed

+120
-128
lines changed

tests/integration/helpers.ts

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,17 @@ interface ParameterInfo {
1818

1919
type ToolInfo = Awaited<ReturnType<Client["listTools"]>>["tools"][number];
2020

21-
export function jestTestMCPClient(): () => Client {
22-
let client: Client | undefined;
23-
let server: Server | undefined;
21+
export function setupIntegrationTest(): {
22+
mcpClient: () => Client;
23+
mongoClient: () => MongoClient;
24+
connectionString: () => string;
25+
connectMcpClient: () => Promise<void>;
26+
} {
27+
let mongoCluster: runner.MongoCluster | undefined;
28+
let mongoClient: MongoClient | undefined;
29+
30+
let mcpClient: Client | undefined;
31+
let mcpServer: Server | undefined;
2432

2533
beforeEach(async () => {
2634
const clientTransport = new InMemoryTransport();
@@ -32,7 +40,7 @@ export function jestTestMCPClient(): () => Client {
3240
clientTransport.output.pipeTo(serverTransport.input);
3341
serverTransport.output.pipeTo(clientTransport.input);
3442

35-
client = new Client(
43+
mcpClient = new Client(
3644
{
3745
name: "test-client",
3846
version: "1.2.3",
@@ -42,41 +50,26 @@ export function jestTestMCPClient(): () => Client {
4250
}
4351
);
4452

45-
server = new Server({
53+
mcpServer = new Server({
4654
mcpServer: new McpServer({
4755
name: "test-server",
4856
version: "1.2.3",
4957
}),
5058
session: new Session(),
5159
});
52-
await server.connect(serverTransport);
53-
await client.connect(clientTransport);
60+
await mcpServer.connect(serverTransport);
61+
await mcpClient.connect(clientTransport);
5462
});
5563

5664
afterEach(async () => {
57-
await client?.close();
58-
client = undefined;
65+
await mcpClient?.close();
66+
mcpClient = undefined;
5967

60-
await server?.close();
61-
server = undefined;
62-
});
68+
await mcpServer?.close();
69+
mcpServer = undefined;
6370

64-
return () => {
65-
if (!client) {
66-
throw new Error("beforeEach() hook not ran yet");
67-
}
68-
69-
return client;
70-
};
71-
}
72-
73-
export function jestTestCluster(): () => { connectionString: string; getClient: () => MongoClient } {
74-
let cluster: runner.MongoCluster | undefined;
75-
let client: MongoClient | undefined;
76-
77-
afterEach(async () => {
78-
await client?.close();
79-
client = undefined;
71+
await mongoClient?.close();
72+
mongoClient = undefined;
8073
});
8174

8275
beforeAll(async function () {
@@ -90,7 +83,7 @@ export function jestTestCluster(): () => { connectionString: string; getClient:
9083
let dbsDir = path.join(tmpDir, "mongodb-runner", "dbs");
9184
for (let i = 0; i < 10; i++) {
9285
try {
93-
cluster = await MongoCluster.start({
86+
mongoCluster = await MongoCluster.start({
9487
tmpDir: dbsDir,
9588
logDir: path.join(tmpDir, "mongodb-runner", "logs"),
9689
topology: "standalone",
@@ -116,25 +109,41 @@ export function jestTestCluster(): () => { connectionString: string; getClient:
116109
}, 120_000);
117110

118111
afterAll(async function () {
119-
await cluster?.close();
120-
cluster = undefined;
112+
await mongoCluster?.close();
113+
mongoCluster = undefined;
121114
});
122115

123-
return () => {
124-
if (!cluster) {
116+
const getMcpClient = () => {
117+
if (!mcpClient) {
118+
throw new Error("beforeEach() hook not ran yet");
119+
}
120+
121+
return mcpClient;
122+
};
123+
124+
const getConnectionString = () => {
125+
if (!mongoCluster) {
125126
throw new Error("beforeAll() hook not ran yet");
126127
}
127128

128-
return {
129-
connectionString: cluster.connectionString,
130-
getClient: () => {
131-
if (!client) {
132-
client = new MongoClient(cluster!.connectionString);
133-
}
129+
return mongoCluster.connectionString;
130+
};
134131

135-
return client;
136-
},
137-
};
132+
return {
133+
mcpClient: getMcpClient,
134+
mongoClient: () => {
135+
if (!mongoClient) {
136+
mongoClient = new MongoClient(getConnectionString());
137+
}
138+
return mongoClient;
139+
},
140+
connectionString: getConnectionString,
141+
connectMcpClient: async () => {
142+
await getMcpClient().callTool({
143+
name: "connect",
144+
arguments: { connectionStringOrClusterName: getConnectionString() },
145+
});
146+
},
138147
};
139148
}
140149

@@ -157,10 +166,10 @@ export function getResponseElements(content: unknown): { type: string; text: str
157166
return response;
158167
}
159168

160-
export async function connect(client: Client, cluster: runner.MongoCluster): Promise<void> {
169+
export async function connect(client: Client, connectionString: string): Promise<void> {
161170
await client.callTool({
162171
name: "connect",
163-
arguments: { connectionStringOrClusterName: cluster.connectionString },
172+
arguments: { connectionStringOrClusterName: connectionString },
164173
});
165174
}
166175

tests/integration/server.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
1-
import { jestTestMCPClient } from "./helpers.js";
1+
import { setupIntegrationTest } from "./helpers";
22

33
describe("Server integration test", () => {
4-
const client = jestTestMCPClient();
4+
const integration = setupIntegrationTest();
55

66
describe("list capabilities", () => {
77
it("should return positive number of tools", async () => {
8-
const tools = await client().listTools();
8+
const tools = await integration.mcpClient().listTools();
99
expect(tools).toBeDefined();
1010
expect(tools.tools.length).toBeGreaterThan(0);
1111
});
1212

1313
it("should return no resources", async () => {
14-
await expect(() => client().listResources()).rejects.toMatchObject({
14+
await expect(() => integration.mcpClient().listResources()).rejects.toMatchObject({
1515
message: "MCP error -32601: Method not found",
1616
});
1717
});
1818

1919
it("should return no prompts", async () => {
20-
await expect(() => client().listPrompts()).rejects.toMatchObject({
20+
await expect(() => integration.mcpClient().listPrompts()).rejects.toMatchObject({
2121
message: "MCP error -32601: Method not found",
2222
});
2323
});
2424

2525
it("should return capabilities", async () => {
26-
const capabilities = client().getServerCapabilities();
26+
const capabilities = integration.mcpClient().getServerCapabilities();
2727
expect(capabilities).toBeDefined();
2828
expect(capabilities?.completions).toBeUndefined();
2929
expect(capabilities?.experimental).toBeUndefined();

tests/integration/tools/mongodb/create/createCollection.test.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
import {
2-
connect,
3-
jestTestCluster,
4-
jestTestMCPClient,
52
getResponseContent,
63
validateParameters,
74
dbOperationParameters,
5+
setupIntegrationTest,
86
} from "../../../helpers.js";
97
import { toIncludeSameMembers } from "jest-extended";
108
import { McpError } from "@modelcontextprotocol/sdk/types.js";
119
import { ObjectId } from "bson";
1210

1311
describe("createCollection tool", () => {
14-
const client = jestTestMCPClient();
15-
const cluster = jestTestCluster();
12+
const integration = setupIntegrationTest();
1613

1714
it("should have correct metadata", async () => {
18-
const { tools } = await client().listTools();
15+
const { tools } = await integration.mcpClient().listTools();
1916
const listCollections = tools.find((tool) => tool.name === "create-collection")!;
2017
expect(listCollections).toBeDefined();
2118
expect(listCollections.description).toBe(
@@ -34,9 +31,9 @@ describe("createCollection tool", () => {
3431
];
3532
for (const arg of args) {
3633
it(`throws a schema error for: ${JSON.stringify(arg)}`, async () => {
37-
await connect(client(), cluster());
34+
await integration.connectMcpClient();
3835
try {
39-
await client().callTool({ name: "create-collection", arguments: arg });
36+
await integration.mcpClient().callTool({ name: "create-collection", arguments: arg });
4037
expect.fail("Expected an error to be thrown");
4138
} catch (error) {
4239
expect(error).toBeInstanceOf(McpError);
@@ -50,12 +47,12 @@ describe("createCollection tool", () => {
5047

5148
describe("with non-existent database", () => {
5249
it("creates a new collection", async () => {
53-
const mongoClient = cluster().getClient();
50+
const mongoClient = integration.mongoClient();
5451
let collections = await mongoClient.db("foo").listCollections().toArray();
5552
expect(collections).toHaveLength(0);
5653

57-
await connect(client(), cluster());
58-
const response = await client().callTool({
54+
await integration.connectMcpClient();
55+
const response = await integration.mcpClient().callTool({
5956
name: "create-collection",
6057
arguments: { database: "foo", collection: "bar" },
6158
});
@@ -75,13 +72,13 @@ describe("createCollection tool", () => {
7572
});
7673

7774
it("creates new collection", async () => {
78-
const mongoClient = cluster().getClient();
75+
const mongoClient = integration.mongoClient();
7976
await mongoClient.db(dbName).createCollection("collection1");
8077
let collections = await mongoClient.db(dbName).listCollections().toArray();
8178
expect(collections).toHaveLength(1);
8279

83-
await connect(client(), cluster());
84-
const response = await client().callTool({
80+
await integration.connectMcpClient();
81+
const response = await integration.mcpClient().callTool({
8582
name: "create-collection",
8683
arguments: { database: dbName, collection: "collection2" },
8784
});
@@ -93,15 +90,15 @@ describe("createCollection tool", () => {
9390
});
9491

9592
it("does nothing if collection already exists", async () => {
96-
const mongoClient = cluster().getClient();
93+
const mongoClient = integration.mongoClient();
9794
await mongoClient.db(dbName).collection("collection1").insertOne({});
9895
let collections = await mongoClient.db(dbName).listCollections().toArray();
9996
expect(collections).toHaveLength(1);
10097
let documents = await mongoClient.db(dbName).collection("collection1").find({}).toArray();
10198
expect(documents).toHaveLength(1);
10299

103-
await connect(client(), cluster());
104-
const response = await client().callTool({
100+
await integration.connectMcpClient();
101+
const response = await integration.mcpClient().callTool({
105102
name: "create-collection",
106103
arguments: { database: dbName, collection: "collection1" },
107104
});

tests/integration/tools/mongodb/metadata/connect.test.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import { getResponseContent, jestTestMCPClient, jestTestCluster, validateParameters } from "../../../helpers.js";
1+
import { getResponseContent, validateParameters, setupIntegrationTest } from "../../../helpers.js";
22

33
import config from "../../../../../src/config.js";
44

55
describe("Connect tool", () => {
6-
const client = jestTestMCPClient();
7-
const cluster = jestTestCluster();
6+
const integration = setupIntegrationTest();
87

98
it("should have correct metadata", async () => {
10-
const { tools } = await client().listTools();
9+
const { tools } = await integration.mcpClient().listTools();
1110
const connectTool = tools.find((tool) => tool.name === "connect")!;
1211
expect(connectTool).toBeDefined();
1312
expect(connectTool.description).toBe("Connect to a MongoDB instance");
@@ -25,7 +24,7 @@ describe("Connect tool", () => {
2524
describe("with default config", () => {
2625
describe("without connection string", () => {
2726
it("prompts for connection string", async () => {
28-
const response = await client().callTool({ name: "connect", arguments: {} });
27+
const response = await integration.mcpClient().callTool({ name: "connect", arguments: {} });
2928
const content = getResponseContent(response.content);
3029
expect(content).toContain("No connection details provided");
3130
expect(content).toContain("mongodb://localhost:27017");
@@ -34,19 +33,19 @@ describe("Connect tool", () => {
3433

3534
describe("with connection string", () => {
3635
it("connects to the database", async () => {
37-
const response = await client().callTool({
36+
const response = await integration.mcpClient().callTool({
3837
name: "connect",
39-
arguments: { connectionStringOrClusterName: cluster().connectionString },
38+
arguments: { connectionStringOrClusterName: integration.connectionString() },
4039
});
4140
const content = getResponseContent(response.content);
4241
expect(content).toContain("Successfully connected");
43-
expect(content).toContain(cluster().connectionString);
42+
expect(content).toContain(integration.connectionString());
4443
});
4544
});
4645

4746
describe("with invalid connection string", () => {
4847
it("returns error message", async () => {
49-
const response = await client().callTool({
48+
const response = await integration.mcpClient().callTool({
5049
name: "connect",
5150
arguments: { connectionStringOrClusterName: "mongodb://localhost:12345" },
5251
});
@@ -61,19 +60,19 @@ describe("Connect tool", () => {
6160

6261
describe("with connection string in config", () => {
6362
beforeEach(async () => {
64-
config.connectionString = cluster().connectionString;
63+
config.connectionString = integration.connectionString();
6564
});
6665

6766
it("uses the connection string from config", async () => {
68-
const response = await client().callTool({ name: "connect", arguments: {} });
67+
const response = await integration.mcpClient().callTool({ name: "connect", arguments: {} });
6968
const content = getResponseContent(response.content);
7069
expect(content).toContain("Successfully connected");
71-
expect(content).toContain(cluster().connectionString);
70+
expect(content).toContain(integration.connectionString());
7271
});
7372

7473
it("prefers connection string from arguments", async () => {
75-
const newConnectionString = `${cluster().connectionString}?appName=foo-bar`;
76-
const response = await client().callTool({
74+
const newConnectionString = `${integration.connectionString()}?appName=foo-bar`;
75+
const response = await integration.mcpClient().callTool({
7776
name: "connect",
7877
arguments: { connectionStringOrClusterName: newConnectionString },
7978
});
@@ -84,7 +83,7 @@ describe("Connect tool", () => {
8483

8584
describe("when the arugment connection string is invalid", () => {
8685
it("suggests the config connection string if set", async () => {
87-
const response = await client().callTool({
86+
const response = await integration.mcpClient().callTool({
8887
name: "connect",
8988
arguments: { connectionStringOrClusterName: "mongodb://localhost:12345" },
9089
});
@@ -97,7 +96,7 @@ describe("Connect tool", () => {
9796

9897
it("returns error message if the config connection string matches the argument", async () => {
9998
config.connectionString = "mongodb://localhost:12345";
100-
const response = await client().callTool({
99+
const response = await integration.mcpClient().callTool({
101100
name: "connect",
102101
arguments: { connectionStringOrClusterName: "mongodb://localhost:12345" },
103102
});

0 commit comments

Comments
 (0)