ardeois / graphql-codegen-typescript-mock-data

[GraphQL Codegen Plugin](https://github.com/dotansimha/graphql-code-generator) for building mock data based on the schema.
MIT License
132 stars 47 forks source link

Option to generate mocks dynamically #85

Closed JimmyPaolini closed 1 year ago

JimmyPaolini commented 1 year ago

I'd like to generate unique mocks for my frontend (React) unit and snapshot tests. It causes issues with key indexing and DOM queries in tests when all the generated mock objects are identical as graphql-codegen-typescript-mock-data currently generates its mocks. I think this can be accomplished by adding an option to dynamically, rather than statically, generate mock data.

For example given a Customer type with its generated mock and a test,

export type Customer {
  id
  name
}

export const aCustomer = (overrides?: Partial<Customer>, _relationshipsToOmit: Array<string> = []): Customer => {
    const relationshipsToOmit = ([..._relationshipsToOmit, 'Customer'])
    return {
        id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'ba708b51-3bcb-43ce-a36f-a878b08d7d0b',
        name: overrides && overrides.hasOwnProperty('name') ? overrides.name! : 'maiores',
    }
}

const mockCustomers = [aCustomer(), aCustomer()]

describe("CustomerList", () => {
  it("renders correctly", () => {
    render(<CustomerList customers={mockCustomers} />)
    expect(screen.getByText(mockCustomers[0].name)).toBeInTheDocument()
  })
})

First React throws issues that the Customer ids should not be the same when they're in a list because it creates indexing issues. Second and more importantly the getByText query fails by finding multiple Customers with the same name. I know that overrides can be used to build distinct mock objects, but I think the need to hardcode such data defeats half the purpose of generating mocks.

My proposed solution is to add a boolean dynamic config option to dynamically, rather than statically, generate mock data. For example with dynamic: true it would output the following mock for Customer:

import casual from 'casual'
export const aCustomer = (overrides?: Partial<Customer>, _relationshipsToOmit: Array<string> = []): Customer => {
    const relationshipsToOmit = ([..._relationshipsToOmit, 'Customer'])
    return {
        id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : casual.uuid,
        name: overrides && overrides.hasOwnProperty('name') ? overrides.name! : casual.name,
    }
}

The user would also need to have some control over the seeding of casual. In the test case described above I would reseed casual before each test is run, that way each test would have mock objects unique within its bounded scope.

I'm very happy to help implement this if it's ok with the maintainer!

ardeois commented 1 year ago

Hi @JimmyPaolini Thanks for the interest on this project and the details you provided, that helps to understand your use case.

It's could be an interesting feature yes, but I'm not sure how you would handle each unit test generate the same mock output every time. Where would you provide the seeding of casual?

JimmyPaolini commented 1 year ago

@ardeois I'm imagining I could run casual.seed('seed1') manually at the top of every test suite or configure my testing library to run it automatically at the beginning of each test suite. The specific seed string doesn't matter as long as its consistent across test runs for each test so it generates consistently unique mocks.

import casual from 'casual'

const mockCustomers = [aCustomer(), aCustomer()]

describe('CustomerList', () => {
  casual.seed('seed')
  it('renders correctly', () => {
    render(<CustomerList customers={mockCustomers} />)
    expect(screen.getByText(mockCustomers[0].name)).toBeInTheDocument()
  })
})

In this solution casual could either be imported from the library itself, or graphql-codegen-typescript-mock-data could export a wrapper function to interanlly seed itself, like export const seedMocks = (seed: string) => casual.seed(seed)

Alternatively, a seed string could be passed into each mock function call in an options object as follows:

const mockCustomers = [
  aCustomer(null, { seed: 'seed1', relationshipsToOmit: [] }), 
  aCustomer(null, { seed: 'seed2' })
]
ardeois commented 1 year ago

Fixed in 2.3.0