Hi, It would be great if you extended this project with a mock Stripe object so that it is possible to write tests using these components without loading the Stripe API.
Thanks for the suggestion @tarjei! This is currently not something we are working on, but we will take it into consideration. Can you share some more details on how you would like to test your app and how Stripe Elements get in your way?
I would also like to re-echo that this would be a very useful addition to the module! My team encountered issues running our tests against any component that has Stripe
@pattishin Hi Patti, could you let me know what approach you guys took to be able to make Stripe element testing possible?
@hoonchoi Unfortunately, we weren't able to sufficiently test the Stripe Elements. 😞 We put it on hold for now and just resorted to shallow testing our child components that required them.
@pattishin Ahhh.. I guess I have to do the same or maybe I'll try injecting the API somehow... Thanks though!
For now, I use this. It makes my tests work, but it's not the complete API. What would be great is a configurable stripe object that could be set so that we could mimmic the outside behaviours of Stripe - i.e. something like:
const Stripe = StripeMock.nextPaymentWillFail('no-funds')
<StripeElement stripe={Stripe} />
Here's what I use now (combined with sinon):
class Element {
mount() {}
update() {}
destroy() {}
on() {}
}
export default function mockStripe() {
const elements = {
create: () => {
return new Element()
}
}
const card = {}
card.mount = sinon.spy()
card.on = sinon.spy()
card.change = sinon.spy()
//elements.create = sinon.stub().returns(card)
const stripe = {
createToken: () => {
console.log('create token')
},
elements: sinon.stub().returns(elements)
}
function Stripe(key) {
stripe.key = key
return stripe
}
return { elements, card, stripe, Stripe }
}
@tarjei oh nice - that's definitely helpful! 🎊 thank you!
I know I'm a bit late to this but I have mocked Stripe the same way that it is being done in the tests for this project. i.e. use original implementations of everything but mock the window.Stripe global variable.
I was able to get a solution hacked together from the suggestions here, would the maintainers be interested in a PR with some documentation on how mock/stub the global Stripe object? I think it would be a nice way to encourage more robust testing practices around billing flows.
Hey @bjudson 👋
That sounds like a great idea for a blog post! We generally reserve the README for officially supported documentation, so we make sure to thoroughly evaluate everything we add to it.
That being said, I'm sure people would still find the work you've done to be valuable, so we strongly encourage that you write about it! And if you do, I'd certainly be curious to read what you have to say.
Thanks for your interest in react-stripe-elements! We're always eager to see what and how people are building with our tools.
If I understand correctly, there are two different kinds of concerns in this thread. I think it would be beneficial to discuss them separately:
Or you can use our async and SSR strategy, which will allow you to gracefully render component trees without a Stripe.js instance, and the Stripe Elements will simply not render.
A second problem is that you might want to actually render Stripe Elements for functional tests and trigger certain behavior, like entering an invalid card number and testing that your integration renders the appropriate error message to the user. In this case, you do need to actually load Stripe.js because you want to test things it does (displaying the element and knowing a card is invalid). The awkward part is actually getting data into the elements, since they cannot accept card numbers as props and their content is rendered in an iframe where it's tricky for you to send events. There are a couple possible solutions here:
I hope this helps anyone who finds this issue and wants help testing their application with react-stripe-elements. We are very aware that this can be difficult and hope to improve the situation over time.
Please comment and let us know if these two issues, and the proposed workarounds, don't encompass everything you're having trouble with.
I think it would be great to have some sort of Mocked version of the StripeProvider component which would make testing parent and children components more thorough
I have found that when I need to test parent components that contain the payment form (for error messages or any UI changes) the easiest way was to simply provide a null value to the Stripeprovider which allows the parent UI to render without the stripe components.
Using Jest + Enzyme
mount(
<StripeProvider stripe={null}>
<ParentComponent />
</StripeProvider>,
);
This allows me to run tests on the parent component and mock out responses from the server and check for UI updates
@asolove-stripe Nice breakdown of the concerns! So I'm looking at the second scenario for e2e testing using Cypress, and I'm trying to fill in each of the CC form fields by drilling into the iframe. By referencing the event handlers in https://github.com/stripe/react-stripe-elements/blob/master/src/components/Element.js, I tried this:
function fillCardNumber(value: string) {
// To enable drilling into the iframe, I'm running Chrome like this:
// open -n -a Google\ Chrome --args --disable-web-security --user-data-dir=/some/dir
const iframe = document.querySelector('[data-test-id="card-number"] iframe')
.contentDocument.documentElement;
// Try to fill in the input here
const input = iframe.querySelector('[name="cardnumber"]');
input.value = value;
// This doesn't seem to do anything
input.dispatchEvent(new Event('change'));
// This wipes whatever that was set by `input.value = value` above
input.dispatchEvent(new Event('blur'));
}
However, upon submitting the form, all the values in the input gets wiped out, same thing on blur. I didn't spot anything in this repo that could be doing this, so I'm guessing this might be something done opaquely by https://js.stripe.com/v3/?
What can I do to make this form submission work?
@thatmarvin: We do not deliberately do anything to clear these inputs and I know of some Stripe users who have tests roughly like this that work.
However, we are aware of problems that prevent some web testing tools from working correctly with controlled React inputs. I'm not familiar with Cyprus, but here's an example in Appium where you can appear to type into inputs during a test, but the React components never get updated due to (screaming noise). I have a suspicion that this may be related to why you are seeing values get wiped out. Do your Cyprus tests work correctly for non-Stripe controlled React inputs?
@asolove-stripe I was manually running commands in the web console, and turns out that React just didn't like the events I was dispatching. However, it worked when I let Cypress set the input values instead. It helped that you ruled out Stripe. Thanks!
@thatmarvin Glad to hear that!
I also think a mock version could be useful. This is how I currently mock Stripe in jest. It was loosely based off of @tarjei's example:
<StripeProvider
stripe={{
createSource: jest.fn(),
elements: () => ({
create: () => ({
mount: jest.fn(),
update: jest.fn(),
destroy: jest.fn(),
on: jest.fn()
})
}),
createToken: jest.fn()
}}>
@klaaspieter Could you elaborate on this example? Where are you inputing your api key? Is this a class you've created? Currently all I'm trying to do is get the very first test that comes with React to pass, the 'it renders without crashing' test. I would love to have a mock wrapper that is initialized and will allow my app to build.
@yasso1am you don't need to provide a token if stripe is set on the StripeProvider. It's what makes loading Stripe async and server side rendering work.
@klaaspieter: interesting solution to this problem, thanks for sharing!
Last version by @klaaspieter is not valid anymore "Please pass a valid Stripe object to StripeProvider." and not matching the typescript definition so you will both get a JS and TS error). It would be great that Stripe would provide at least something as simple to provide as the mock object you use in your test.
https://github.com/stripe/react-stripe-elements/blob/master/src/components/Provider.test.js#L12
They use this for their tests
stripeMockResult = {
elements: jest.fn(),
createToken: jest.fn(),
createSource: jest.fn(),
createPaymentMethod: jest.fn(),
handleCardPayment: jest.fn(),
};
I keep getting Error: Please pass either 'apiKey' or 'stripe' to StripeProvider. If you're using 'stripe' but don't have a Stripe instance yet, pass 'null' explicitly. even though am using it the way they are testing in https://github.com/stripe/react-stripe-elements/blob/master/src/components/Provider.test.js#L12
import React from 'react';
import { render, cleanup } from '@testing-library/react';
import { Elements, StripeProvider } from 'react-stripe-elements';
import * as nextRouter from 'next/router';
import Buy from '../Buy';
import CheckoutContext from '../../utils/context';
describe('Buy component tests', () => {
let elementMock;
let elementsMock;
let stripeMock;
beforeEach(() => {
elementMock = {
mount: jest.fn(),
destroy: jest.fn(),
on: jest.fn(),
update: jest.fn()
};
elementsMock = {
create: jest.fn().mockReturnValue(elementMock)
};
stripeMock = {
elements: jest.fn().mockReturnValue(elementsMock),
createToken: jest.fn(),
createSource: jest.fn(),
createPaymentMethod: jest.fn(),
handleCardPayment: jest.fn(),
handleCardSetup: jest.fn()
};
window.Stripe = jest.fn().mockReturnValue(stripeMock);
});
afterEach(cleanup);
it('should render the CheckoutForm', () => {
const checkoutContextValues = {};
const tree = render(
<CheckoutContext.Provider value={checkoutContextValues}>
<StripeProvider apiKey='pk_test_xxx'>
<Elements>
<Buy />
</Elements>
</StripeProvider>
</CheckoutContext.Provider>
);
console.log('Tree', tree.debug());
});
});
Component to test
return (
<StripeProvider apiKey={apiKey}>
<Elements>
{checkout.shipping_preference === ShippingPreference.IN_STORE || allCartProductsDownloadable(cart) ? (
<CreditCardForm />
) : (
<CreditCardAndShippingForm />
)}
</Elements>
</StripeProvider>
);
Hey @theghostyced mocking window.Stripe works just fine—I was able to use Jest with the latest version of react-stripe-elements without issue. It's not clear what component your "Component to test" code corresponds with, but looks like it's creating its own StripeProvider and throwing an error because the apiKey prop wasn't passed in to your component.
You should only create one StripeProvider in your component tree. Rather than create StripeProvider in the Buy component, for example, you should have the Buy component assume it's mounted beneath a StripeProvider+Elements pair and create a CardElement, etc:
…
it('should render the CheckoutForm', () => {
const checkoutContextValues = {};
const tree = render(
<CheckoutContext.Provider value={checkoutContextValues}>
<StripeProvider apiKey='pk_test_xxx'>
<Elements>
<Buy />
</Elements>
</StripeProvider>
</CheckoutContext.Provider>
);
});
// … elsewhere …
class Buy extends React.Component {
function render() {
return <>{checkout.shipping_preference === ShippingPreference.IN_STORE || allCartProductsDownloadable(cart) ? (
<CreditCardForm />
) : (
<CreditCardAndShippingForm />
)}</>;
}
}
Hey @fred-stripe Funny thing is I was about to try this, thanks for the response, will let you know how it goes :)
Closing this as it's a relatively old issue and this project has migrated to React Stripe.js. If you believe this is still important, please re-open it there.
Most helpful comment
For now, I use this. It makes my tests work, but it's not the complete API. What would be great is a configurable stripe object that could be set so that we could mimmic the outside behaviours of Stripe - i.e. something like:
Here's what I use now (combined with sinon):