React-stripe-elements: Testing Stripe with react-testing-library

Created on 6 Sep 2019  路  8Comments  路  Source: stripe/react-stripe-elements

Summary

Hi, we're trying to test our new checkout that is using Stripe with react-stripe-elements using react-testing-library.

Our problem at the moment is that when we try to render the application it crashes and I traced it down to the injectStripe HOC that wraps our payment form. As soon as I use CardNumberElement, CardExpiryElement or CardCvcElement react-testing-library crashes when rendering.

What I had to do to get this far:

  • I had to download the stripe.js lib and require it manually in our setupTests file (ew, i know but loading a script doesn't work in jest)
  • Wrap the whole test application in the StripeProvider and set a test apiKey

Any thoughts on how to make this work? I couldn't find _any_ documentation around testing applications that use react-stripe-elements which I found slightly shocking. Might be good to add in some documentation around that in the future (happy to contribute once a proper method has been established)

Look forward to hearing from you!

Thanks :)

Most helpful comment

Here is what I did and it works for me. What do you folks think about this?

jest.mock('react-stripe-elements', () => {
    return {
        injectStripe: () => {
            const PremiumCheckoutFormTest2 = require('./PremiumCheckoutForm').PremiumCheckoutFormTest;
            return PremiumCheckoutFormTest2;
        },
        CardElement: () => {
            return <div>CardElement</div>;
        },
    };
});

Sources:

All 8 comments

Also experiencing a very similar issue to the one explained above ^

Hey! I don't remember where exactly I found this solution but it has worked great so far. Let me know if it works for you.

1) Mock the functions used in the Stripe Elements and Stripe Provider.
2) Set window.Stripe to return the Stripe mock.
3) You will still need to wrap your components in the Stripe provider and pass it an API key.
4) You create a token creation response by mocking Stripe's createToken function.

Here's an example of a test:

it('Charges a card', () => {
  // Arrange

  // Mocking Stripe object
  const elementMock = {
    mount: jest.fn(),
    destroy: jest.fn(),
    on: jest.fn(),
    update: jest.fn(),
  };

  const elementsMock = {
    create: jest.fn().mockReturnValue(elementMock),
  };

  const stripeMock = {
    elements: jest.fn().mockReturnValue(elementsMock),
    createToken: jest.fn(() => Promise.resolve()),
    createSource: jest.fn(() => Promise.resolve()),
  };

  // Set the global Stripe
  window.Stripe = jest.fn().mockReturnValue(stripeMock);

  // Ex. of a token successfully created mock
  stripeMock.createToken.mockResolvedValue({
    token: {
      id: 'test_id',
    },
  });

  // Ex. of a failure mock
  stripeMock.createToken.mockResolvedValue({
    error: {
      code: 'incomplete_number',
      message: 'Your card number is incomplete.',
      type: 'validation_error',
    },
  });

 // Act
 // Wrap component in provider
  const { getByText } = render(
    <StripeProvider apiKey="random_key">
      <ComponentWithStripeElements />
    </StripeProvider>
  )

  // Do stuff with the components, api calls, etc as you would normally do with React Testing Library
})

Hey man!

Awesome, thank you SO much for your reply! I actually got it to work but what I'm struggling with is setting the values for the different stripe card elements.

What I've tried was trying to get the stripe input by the input name so something like
const cardNumberInput = container.querySelector('input[name=cardnumber]')
but that returns null.
The problem seems to be that when I run the debug() method from react-testing-library on the payment form component (which contains the elements) the actual stripe inputs are not being rendered.

Would you mind sharing your snippet and how you actually fill in data?

Thanks a lot again, this really helped!

David

Actually, after looking over our tests, it seems like we do not add the credit card information on the Stripe elements with RTL (I remember attempting to do this as well but it never did work out). If I'm not mistaken, Stripe hosts the credit card form inputs on their server. So it's not rendered with React. I'm not sure about the details of how Stripe React Elements actually gets that data and passes it to the provider though.

In the end, we just trusted the library's test suite to ensure that it gets the input correctly. (Which isn't ideal but their existing tests put our minds at ease a bit). The only thing we mock is the response from the createToken API call to Stripe.com.

If our form has other values (such as name, address, etc), we fill those out with RTL. And then we just move to the submit phase and test the failure and success scenarios.

Sorry that I couldn't be of more help. Perhaps one of the library's maintainers can provide some more insight. Good luck!

Aaah ok!
Interesting.. I would be fine with that if our "Pay" button wasn't enabled unless the user enters their credit card information...

I wonder if there's any way around it, for example mocking the stripe input in test and replacing it with a standard input or something like that...

Thanks a lot for your help anyways!! Hopefully the stripe maintainers chime in soon

Here is what I did and it works for me. What do you folks think about this?

jest.mock('react-stripe-elements', () => {
    return {
        injectStripe: () => {
            const PremiumCheckoutFormTest2 = require('./PremiumCheckoutForm').PremiumCheckoutFormTest;
            return PremiumCheckoutFormTest2;
        },
        CardElement: () => {
            return <div>CardElement</div>;
        },
    };
});

Sources:

@ademidun answer worked for me as well. I also added mocking the <Elements> component from react-stripe-elements

jest.mock('react-stripe-elements', () => {
    return {
        injectStripe: () => {
            const CardFormWithOutStripeInjection = require('./StripeCardForm').CardFormWithOutStripeInjection;
            return CardFormWithOutStripeInjection;
        },
        CardElement: () => {
            return <div>CardElement</div>;
        },
        Elements: ({children}) => {
            return (
                <div>
                    {children}
                </div>
            )
        }
    };
});

As an example, I've included the imported CardForm component below.

import { CardElement, injectStripe } from 'react-stripe-elements';

class CardForm extends React.Component<ComponentProps> {
    render() {
        return (
            <div data-testid="pay-with-stripe-form">
                    <CardElement
                        onChange={}

                    />
            </div>
        );
    }
}
export { CardForm as CardFormWithOutStripeInjection };
export default injectStripe(CardForm);

Thanks for the great suggestions in the thread, everybody! Closing this as this project has migrated to React Stripe.js. If you believe more is still needed in this area, please re-open it there.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sonarforte picture sonarforte  路  4Comments

kongakong picture kongakong  路  4Comments

max-favilli picture max-favilli  路  5Comments

abachuk picture abachuk  路  3Comments

mmmikeal picture mmmikeal  路  4Comments