Skip to content

Commit d4ce855

Browse files
authored
Merge branch 'main' into feat/dbauth-fetch-handler
2 parents ed74729 + fca8d7a commit d4ce855

File tree

28 files changed

+558
-212
lines changed

28 files changed

+558
-212
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { Prisma, Contact } from '@prisma/client'
2+
3+
import type { ScenarioData } from '@redwoodjs/testing/api'
4+
5+
export const standard = defineScenario<Prisma.ContactCreateArgs>({
6+
contact: {
7+
one: { data: { name: 'String', email: 'String', message: 'String' } },
8+
two: { data: { name: 'String', email: 'String', message: 'String' } },
9+
},
10+
})
11+
12+
export type StandardScenario = ScenarioData<Contact, 'contact'>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { db } from 'src/lib/db'
2+
3+
import { contact, contacts, createContact } from './contacts'
4+
import type { StandardScenario } from './contacts.scenarios'
5+
6+
/**
7+
* Example test for describe scenario.
8+
*
9+
* Note that scenario tests need a matching [name].scenarios.ts file.
10+
*/
11+
12+
describeScenario<StandardScenario>('contacts', (getScenario) => {
13+
let scenario: StandardScenario
14+
15+
beforeEach(() => {
16+
scenario = getScenario()
17+
})
18+
19+
it('returns all contacts', async () => {
20+
const result = await contacts()
21+
22+
expect(result.length).toEqual(Object.keys(scenario.contact).length)
23+
})
24+
25+
it('returns a single contact', async () => {
26+
const result = await contact({ id: scenario.contact.one.id })
27+
28+
expect(result).toEqual(scenario.contact.one)
29+
})
30+
31+
it('creates a contact', async () => {
32+
const result = await createContact({
33+
input: {
34+
name: 'Bazinga',
35+
email: 'contact@describe.scenario',
36+
message: 'Describe scenario works!',
37+
},
38+
})
39+
40+
expect(result.name).toEqual('Bazinga')
41+
expect(result.email).toEqual('contact@describe.scenario')
42+
expect(result.message).toEqual('Describe scenario works!')
43+
})
44+
45+
it('Checking that describe scenario works', async () => {
46+
// This test is dependent on the above test. If you used a normal scenario it would not work
47+
const contactCreatedInAboveTest = await db.contact.findFirst({
48+
where: {
49+
email: 'contact@describe.scenario',
50+
},
51+
})
52+
53+
expect(contactCreatedInAboveTest.message).toEqual(
54+
'Describe scenario works!'
55+
)
56+
})
57+
})

docs/docs/testing.md

+101
Original file line numberDiff line numberDiff line change
@@ -1696,6 +1696,107 @@ Only the posts scenarios will be present in the database when running the `posts
16961696
16971697
During the run of any single test, there is only ever one scenario's worth of data present in the database: users.standard *or* users.incomplete.
16981698
1699+
### describeScenario - a performance optimisation
1700+
1701+
The scenario feature described above should be the base starting point for setting up test that depend on the database. The scenario sets up the database before each scenario _test_, runs the test, and then tears down (deletes) the database scenario. This ensures that each of your tests are isolated, and that they do not affect each other.
1702+
1703+
**However**, there are some situations where you as the developer may want additional control regarding when the database is setup and torn down - maybe to run your test suite faster.
1704+
1705+
The `describeScenario` function is utilized to run a sequence of multiple tests, with a single database setup and tear-down.
1706+
1707+
```js
1708+
// highlight-next-line
1709+
describeScenario('contacts', (getScenario) => {
1710+
// You can imagine the scenario setup happens here
1711+
1712+
// All these tests now use the same setup 👇
1713+
it('xxx', () => {
1714+
// Notice that the scenario has to be retrieved using the getter
1715+
// highlight-next-line
1716+
const scenario = getScenario()
1717+
//...
1718+
})
1719+
1720+
it('xxx', () => {
1721+
const scenario = getScenario()
1722+
/...
1723+
})
1724+
1725+
})
1726+
```
1727+
1728+
> **CAUTION**: With describeScenario, your tests are no longer isolated. The results, or side-effects, of prior tests can affect later tests.
1729+
1730+
Rationale for using `describeScenario` include:
1731+
<ul>
1732+
<li>Create multi-step tests where the next test is dependent upon the results of the previous test (Note caution above).</li>
1733+
<li>Reduce testing run time. There is an overhead to setting up and tearing down the db on each test, and in some cases a reduced testing run time may be of significant benefit. This may be of benefit where the likelihood of side-effects is low, such as in query testing</li>
1734+
</ul>
1735+
1736+
### describeScenario Examples
1737+
1738+
Following is an example of the use of `describeScenario` to speed up testing of a user query service function, where the risk of side-effects is low.
1739+
1740+
```ts
1741+
// highlight-next-line
1742+
describeScenario<StandardScenario>('user query service', (getScenario) => {
1743+
1744+
let scenario: StandardScenario
1745+
1746+
beforeEach(() => {
1747+
// Grab the scenario before each test
1748+
// highlight-next-line
1749+
scenario = getScenario()
1750+
})
1751+
1752+
it('retrieves a single user for a validated user', async () => {
1753+
mockCurrentUser({ id: 123, name: 'Admin' })
1754+
1755+
const record = await user({ id: scenario.user.dom.id })
1756+
1757+
expect(record.id).toEqual(scenario.user.dom.id)
1758+
})
1759+
1760+
it('throws an error upon an invalid user id', async () => {
1761+
mockCurrentUser({ id: 123, name: 'Admin' })
1762+
1763+
const fcn = async () => await user({ id: null as unknown as number })
1764+
1765+
await expect(fcn).rejects.toThrow()
1766+
})
1767+
1768+
it('throws an error if not authenticated', async () => {
1769+
const fcn = async () => await user({ id: scenario.user.dom.id })
1770+
1771+
await expect(fcn).rejects.toThrow(AuthenticationError)
1772+
})
1773+
1774+
it('throws an error if the user is not authorized to query the user', async () => {
1775+
mockCurrentUser({ id: 999, name: 'BaseLevelUser' })
1776+
1777+
const fcn = async () => await user({ id: scenario.user.dom.id })
1778+
1779+
await expect(fcn).rejects.toThrow(ForbiddenError)
1780+
})
1781+
})
1782+
```
1783+
1784+
:::tip Using named scenarios with describeScenario
1785+
1786+
If you have multiple scenarios, you can also use named scenario with `describeScenario`
1787+
1788+
For example:
1789+
```js
1790+
// If we have a paymentDeclined scenario defined in the .scenario.{js,ts} file
1791+
// The second parameter is the name of the "describe" block
1792+
describeScenario('paymentDeclined', 'Retrieving details', () => {
1793+
// ....
1794+
})
1795+
```
1796+
:::
1797+
1798+
1799+
16991800
### mockCurrentUser() on the API-side
17001801
17011802
Just like when testing the web-side, we can use `mockCurrentUser()` to mock out the user that's currently logged in (or not) on the api-side.
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import type { Scenario, DefineScenario } from '@redwoodjs/testing/api'
1+
import type { Scenario, DefineScenario, DescribeScenario } from '@redwoodjs/testing/api'
22

33
declare global {
44
/**
55
* Note that the scenario name must match the exports in your {model}.scenarios.ts file
66
*/
77
const scenario: Scenario
8+
const describeScenario: DescribeScenario
89
const defineScenario: DefineScenario
910
}

packages/mailer/core/package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
"build:types": "tsc --build --verbose",
1919
"build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx\" --ignore dist --exec \"yarn build\"",
2020
"prepublishOnly": "NODE_ENV=production yarn build",
21-
"test": "jest src",
22-
"test:watch": "yarn test --watch"
21+
"test": "vitest run src",
22+
"test:watch": "vitest watch src"
2323
},
2424
"jest": {
2525
"testPathIgnorePatterns": [
@@ -30,8 +30,8 @@
3030
"@redwoodjs/api": "6.0.7",
3131
"esbuild": "0.19.9",
3232
"fast-glob": "3.3.2",
33-
"jest": "29.7.0",
34-
"typescript": "5.3.3"
33+
"typescript": "5.3.3",
34+
"vitest": "1.2.1"
3535
},
3636
"gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1"
3737
}

0 commit comments

Comments
 (0)