-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
setupTestSuiteMatrix.ts
139 lines (122 loc) · 5.48 KB
/
setupTestSuiteMatrix.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import fs from 'fs-extra'
import path from 'path'
import { checkMissingProviders } from './checkMissingProviders'
import { getTestSuiteConfigs, getTestSuiteFolderPath, getTestSuiteMeta } from './getTestSuiteInfo'
import { getTestSuitePlan } from './getTestSuitePlan'
import { getClientMeta, setupTestSuiteClient } from './setupTestSuiteClient'
import { dropTestSuiteDatabase, setupTestSuiteDbURI } from './setupTestSuiteEnv'
import { stopMiniProxyQueryEngine } from './stopMiniProxyQueryEngine'
import { ClientMeta, MatrixOptions } from './types'
export type TestSuiteMeta = ReturnType<typeof getTestSuiteMeta>
/**
* How does this work from a high level? What steps?
* 1. You create a file that uses `setupTestSuiteMatrix`
* 2. It defines a test suite, but it is a special one
* 3. You create a `_matrix.ts` near your test suite
* 4. This defines the test suite matrix to be used
* 5. You write a few tests inside your test suite
* 7. Execute tests like you usually would with jest
* 9. The test suite expands into many via the matrix
* 10. Each test suite has it's own generated schema
* 11. Each test suite has it's own database, and env
* 12. Each test suite has it's own generated client
* 13. Each test suite is executed with those files
* 14. Each test suite has its environment cleaned up
*
* @remarks Why does each test suite have a generated schema? This is to support
* multi-provider testing and more. A base schema is automatically injected with
* the cross-product of the configs defined in `_matrix.ts` (@see _example).
*
* @remarks Generated files are used for getting the test ready for execution
* (writing the schema, the generated client, etc...). After the test are done
* executing, the files can easily be submitted for type checking.
*
* @remarks Treat `_matrix.ts` as being analogous to a github action matrix.
*
* @remarks Jest snapshots will work out of the box, but not inline snapshots
* because those can't work in a loop (jest limitation). To make it work, you
* just need to pass `-u` to jest and we do the magic to make it work.
*
* @param tests where you write your tests
*/
function setupTestSuiteMatrix(
tests: (suiteConfig: Record<string, string>, suiteMeta: TestSuiteMeta, clientMeta: ClientMeta) => void,
options?: MatrixOptions,
) {
const originalEnv = process.env
const suiteMeta = getTestSuiteMeta()
const clientMeta = getClientMeta()
const suiteConfigs = getTestSuiteConfigs(suiteMeta)
const testPlan = getTestSuitePlan(suiteMeta, suiteConfigs, clientMeta, options)
checkMissingProviders({
suiteConfigs,
suiteMeta,
options,
})
for (const { name, suiteConfig, skip } of testPlan) {
const describeFn = skip ? describe.skip : describe
describeFn(name, () => {
const clients = [] as any[]
// we inject modified env vars, and make the client available as globals
beforeAll(async () => {
const datasourceInfo = setupTestSuiteDbURI(suiteConfig.matrixOptions, clientMeta)
globalThis['datasourceInfo'] = datasourceInfo
globalThis['loaded'] = await setupTestSuiteClient({
suiteMeta,
suiteConfig,
skipDb: options?.skipDb,
datasourceInfo,
clientMeta,
alterStatementCallback: options?.alterStatementCallback,
})
globalThis['newPrismaClient'] = (...args) => {
const client = new global['loaded']['PrismaClient'](...args)
clients.push(client)
return client
}
if (!options?.skipDefaultClientInstance) {
globalThis['prisma'] = globalThis['newPrismaClient']()
}
globalThis['Prisma'] = (await global['loaded'])['Prisma']
})
// for better type dx, copy a client into the test suite root node_modules
// this is so that we can have intellisense for the client in the test suite
beforeAll(() => {
const rootNodeModuleFolderPath = path.join(suiteMeta.testRoot, 'node_modules')
// reserve the node_modules so that parallel tests suites don't conflict
fs.mkdir(rootNodeModuleFolderPath, async (error) => {
if (error !== null && error.code !== 'EEXIST') throw error // unknown error
if (error !== null && error.code === 'EEXIST') return // already reserved
const suiteFolderPath = getTestSuiteFolderPath(suiteMeta, suiteConfig)
const suiteNodeModuleFolderPath = path.join(suiteFolderPath, 'node_modules')
await fs.copy(suiteNodeModuleFolderPath, rootNodeModuleFolderPath, { recursive: true })
})
})
afterAll(async () => {
for (const client of clients) {
await client.$disconnect().catch(() => {
// sometimes we test connection errors. In that case,
// disconnect might also fail, so ignoring the error here
})
if (clientMeta.dataProxy) {
await stopMiniProxyQueryEngine(client)
}
}
clients.length = 0
if (!options?.skipDb) {
const datasourceInfo = globalThis['datasourceInfo']
process.env[datasourceInfo.envVarName] = datasourceInfo.databaseUrl
await dropTestSuiteDatabase(suiteMeta, suiteConfig)
}
process.env = originalEnv
delete globalThis['datasourceInfo']
delete globalThis['loaded']
delete globalThis['prisma']
delete globalThis['Prisma']
delete globalThis['newPrismaClient']
}, 120_000)
tests(suiteConfig.matrixOptions, suiteMeta, clientMeta)
})
}
}
export { setupTestSuiteMatrix }