redwood
redwood copied to clipboard
API Testing - Scenario Types
The scenario generator works great, and even has basic typescript support!
However, when functions are embedded into the scenario creation object to support relations, the redwood typescript fails. For example, I'm trying to set up the following scenario:
export const standard = defineScenario ({
organization: {
orgA: (): Prisma.OrganizationCreateArgs => ({
data: {
name: 'Org A',
},
}),
},
user: {
userA: (scenario): Prisma.UserCreateArgs => ({
data: {
nameFirst: 'Bob',
nameLast: 'Builder',
email: '[email protected]',
phone: '123-456-7890',
title: 'boss',
roles: 'abc,def',
active: true,
uid: 'aaa',
organizationId: scenario.organization.orgA.id,
},
}),
},
project: {
projectA: (): Prisma.ProjectCreateArgs => ({
data: {
name: 'Project 1',
description: 'Project 1 desc',
owner: 'Project 1 Owner',
},
}),
},
milestone: {
one: (scenario): Prisma.MilestoneCreateArgs => ({
data: {
name: 'String',
createdById: scenario.user.userA.id,
dateTargetActive: true,
dateTarget: '2021-06-07T21:02:43Z',
dateActual: '2021-06-07T21:02:43Z',
projectId: scenario.project.projectA.id,
},
}),
two: (scenario): Prisma.MilestoneCreateArgs => ({
data: {
name: 'String',
createdById: scenario.user.userA.id,
dateTargetActive: true,
dateTarget: '2021-06-07T21:02:43Z',
dateActual: '2021-06-10T21:02:43Z',
projectId: scenario.project.projectA.id,
},
}),
},
})
Because of the functions to resolve the relations (which is an elegant system), the typescript blows up. For now, I have resolved the situation manually by not using the Redwood defineScenario
function to create the types and manually typing the seeded result.
import type { Organization, Milestone, Project, User } from '@prisma/client'
export const standard = {...}
export type StandardScenario = {
organization: {
orgA: Organization
}
user: {
userA: User
}
project: {
projectA: Project
}
milestone: {
one: Milestone
two: Milestone
}
}
If one of our typescript gurus could find an automatic way of typing the StandardScenario
type from the standard
definition , that would be awesome! I took a stab at it, but this goes past my current Typescript knowledge! If for some reason this proves challenging, then at a minimum we should probably update the docs regarding the typescript support.
Thanks!
@cjreimer just to confirm, in your example, the scenario is for your Organization
model (e.g. the file's called organization.scenarios.ts
and defineScenario
initially had a Prisma generic passed to it, like defineScenario<Prisma.OrganizationCreateArgs>
), but since you're creating User
s and Project
s in the same scenario (which is totally allowed!) the typing breaks down in two ways:
- the relation functions aren't being appropriately typed (which is why you have to be explicit about the return types in your example above), and
- the exported type
StandardScenario
is wrong?
@jtoar, thanks for looking into this!
Yes, I think you got it. I'll clarify a bit more, numbered as per your comments:
- By default, the scaffold generator creates a scenario using the following line:
export const standard = defineScenario<Prisma.ModelCreateArgs>({
As we need to create a multi-model scenario, this definition is no longer correct. It's easy enough to get rid of the type on the defineScenario
function and type each function as shown in my example above to make sure that the entry is typed correctly. It would be great if the scaffold generator could do this by default, but this would be the lower priority of the two issues in my mind, but it may be the easier issue to change.
- The type of the exported
StandardScenario
would be for me the higher priority. When using a multi-model scenario, I had to completely replace redwoods automatically generatedStandardScenario
with a manual one, as shown in the original post. It would be great if we could create a generic type system to create the StandardScenario type. The trick is that the resulting objects under the user property for example are no longer aPrisma.UserCreateArgs
, but rather a prismaUser
type.
Other than for the most basic apps, I believe that multi-model scenarios are the most common use case. Note that I've also stitched together multiple model scenario files as follows:
import { standard as milestoneStandard } from ....
export const standard = {
...milestoneStandard,
requirement: {...}
}
Does that clarify things?
@cjreimer yes it does thank you very much!
I think this is slightly incorrect:
export type StandardScenario = {
organization: {
orgA: Organization
}
user: {
userA: User
}
project: {
projectA: Project
}
milestone: {
one: Milestone
two: Milestone
}
}
Couldn't we write it more precisely, generically and concisely like this?
export type StandardScenario = {
organization: Record<string, Prisma.OrganizationCreateArgs['data']>,
user: Record<string, Prisma.UserCreateArgs['data']>,
project: Record<string, Prisma.ProjectCreateArgs['data']>,
milestone: Record<string, Prisma.MilestoneCreateArgs['data']>,
}
any update on best practices here?
This works for strongly typing named instances of a model:
const standardOrganization = {
testOrganization: {
data: {
name: 'Test',
},
} as Prisma.OrganizationCreateArgs,
}
...
export const standard = {
organization: standardOrganization,
space: standardSpace,
type: standardType,
content: standardContent,
}
export type StandardScenario = {
organization: Record<keyof typeof standardOrganization, Organization>
space: Record<keyof typeof standardSpace, Space>
type: Record<keyof typeof standardType, Type>
content: Record<keyof typeof standardContent, Content>
}
Hi! Are there any updates on this? Running into the same issues :)
I can't even compost out the scenario data and pass them into defineScenario
while having it typed, or I get a "type is possibly infinitely deep" error. Specifically, I tried doing this:
Hi! Are there any updates on this? Running into the same issues :)
I can't even compost out the scenario data and pass them into
defineScenario
while having it typed, or I get a "type is possibly infinitely deep" error. Specifically, I tried doing this:
You don’t need defineScenario. See my example above
OH I'm not sure how I totally missed that - will certainly give it a go, curious for the best practices as well. Thanks!
Wait - how does your example work with inlining functions to create relations? That's the main issue.
Wait - how does your example work with inlining functions to create relations? That's the main issue.
Here is an example:
const standardSpace = {
testSpace: (scenario: StandardScenario): Prisma.SpaceCreateArgs => ({
data: {
name: 'Test',
organizationId: scenario.organization.testOrganization.id,
},
}),
}
This is a better solution than using defineScenario for sure, but gets extremely verbose if you're creating multiple models in each scenario and using them in other scenarios. Curious for the redwood team to weigh in on this.
This is a better solution than using defineScenario for sure, but gets extremely verbose if you're creating multiple models in each scenario and using them in other scenarios. Curious for the redwood team to weigh in on this.
Repeated LOC is O(N) where N is # of models. I don’t think you can do better than that though.
on second thought, maybe O(1) is possible with some TypeScript tricks
Hi there. How about directly calling the db.[model].create or defined repository.create before the test code, without using the scenario feature?