Skip to content

Commit

Permalink
Merge branch 'main' into feat/dbauth-fetch-handler
Browse files Browse the repository at this point in the history
  • Loading branch information
dac09 committed Jan 22, 2024
2 parents ed74729 + fca8d7a commit d4ce855
Show file tree
Hide file tree
Showing 28 changed files with 558 additions and 212 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Prisma, Contact } from '@prisma/client'

import type { ScenarioData } from '@redwoodjs/testing/api'

export const standard = defineScenario<Prisma.ContactCreateArgs>({
contact: {
one: { data: { name: 'String', email: 'String', message: 'String' } },
two: { data: { name: 'String', email: 'String', message: 'String' } },
},
})

export type StandardScenario = ScenarioData<Contact, 'contact'>
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { db } from 'src/lib/db'

import { contact, contacts, createContact } from './contacts'
import type { StandardScenario } from './contacts.scenarios'

/**
* Example test for describe scenario.
*
* Note that scenario tests need a matching [name].scenarios.ts file.
*/

describeScenario<StandardScenario>('contacts', (getScenario) => {
let scenario: StandardScenario

beforeEach(() => {
scenario = getScenario()
})

it('returns all contacts', async () => {
const result = await contacts()

expect(result.length).toEqual(Object.keys(scenario.contact).length)
})

it('returns a single contact', async () => {
const result = await contact({ id: scenario.contact.one.id })

expect(result).toEqual(scenario.contact.one)
})

it('creates a contact', async () => {
const result = await createContact({
input: {
name: 'Bazinga',
email: 'contact@describe.scenario',
message: 'Describe scenario works!',
},
})

expect(result.name).toEqual('Bazinga')
expect(result.email).toEqual('contact@describe.scenario')
expect(result.message).toEqual('Describe scenario works!')
})

it('Checking that describe scenario works', async () => {
// This test is dependent on the above test. If you used a normal scenario it would not work
const contactCreatedInAboveTest = await db.contact.findFirst({
where: {
email: 'contact@describe.scenario',
},
})

expect(contactCreatedInAboveTest.message).toEqual(
'Describe scenario works!'
)
})
})
101 changes: 101 additions & 0 deletions docs/docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -1696,6 +1696,107 @@ Only the posts scenarios will be present in the database when running the `posts
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.
### describeScenario - a performance optimisation
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.
**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.
The `describeScenario` function is utilized to run a sequence of multiple tests, with a single database setup and tear-down.
```js
// highlight-next-line
describeScenario('contacts', (getScenario) => {
// You can imagine the scenario setup happens here

// All these tests now use the same setup 👇
it('xxx', () => {
// Notice that the scenario has to be retrieved using the getter
// highlight-next-line
const scenario = getScenario()
//...
})

it('xxx', () => {
const scenario = getScenario()
/...
})

})
```
> **CAUTION**: With describeScenario, your tests are no longer isolated. The results, or side-effects, of prior tests can affect later tests.
Rationale for using `describeScenario` include:
<ul>
<li>Create multi-step tests where the next test is dependent upon the results of the previous test (Note caution above).</li>
<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>
</ul>
### describeScenario Examples
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.
```ts
// highlight-next-line
describeScenario<StandardScenario>('user query service', (getScenario) => {

let scenario: StandardScenario

beforeEach(() => {
// Grab the scenario before each test
// highlight-next-line
scenario = getScenario()
})

it('retrieves a single user for a validated user', async () => {
mockCurrentUser({ id: 123, name: 'Admin' })

const record = await user({ id: scenario.user.dom.id })

expect(record.id).toEqual(scenario.user.dom.id)
})

it('throws an error upon an invalid user id', async () => {
mockCurrentUser({ id: 123, name: 'Admin' })

const fcn = async () => await user({ id: null as unknown as number })

await expect(fcn).rejects.toThrow()
})

it('throws an error if not authenticated', async () => {
const fcn = async () => await user({ id: scenario.user.dom.id })

await expect(fcn).rejects.toThrow(AuthenticationError)
})

it('throws an error if the user is not authorized to query the user', async () => {
mockCurrentUser({ id: 999, name: 'BaseLevelUser' })

const fcn = async () => await user({ id: scenario.user.dom.id })

await expect(fcn).rejects.toThrow(ForbiddenError)
})
})
```
:::tip Using named scenarios with describeScenario
If you have multiple scenarios, you can also use named scenario with `describeScenario`
For example:
```js
// If we have a paymentDeclined scenario defined in the .scenario.{js,ts} file
// The second parameter is the name of the "describe" block
describeScenario('paymentDeclined', 'Retrieving details', () => {
// ....
})
```
:::
### mockCurrentUser() on the API-side
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { Scenario, DefineScenario } from '@redwoodjs/testing/api'
import type { Scenario, DefineScenario, DescribeScenario } from '@redwoodjs/testing/api'

declare global {
/**
* Note that the scenario name must match the exports in your {model}.scenarios.ts file
*/
const scenario: Scenario
const describeScenario: DescribeScenario
const defineScenario: DefineScenario
}
8 changes: 4 additions & 4 deletions packages/mailer/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"build:types": "tsc --build --verbose",
"build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx\" --ignore dist --exec \"yarn build\"",
"prepublishOnly": "NODE_ENV=production yarn build",
"test": "jest src",
"test:watch": "yarn test --watch"
"test": "vitest run src",
"test:watch": "vitest watch src"
},
"jest": {
"testPathIgnorePatterns": [
Expand All @@ -30,8 +30,8 @@
"@redwoodjs/api": "6.0.7",
"esbuild": "0.19.9",
"fast-glob": "3.3.2",
"jest": "29.7.0",
"typescript": "5.3.3"
"typescript": "5.3.3",
"vitest": "1.2.1"
},
"gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1"
}
Loading

0 comments on commit d4ce855

Please sign in to comment.