enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.95k stars 2.01k forks source link

Use Enzyme for Integration Testing (end-to-end testing) #237

Open lelandrichardson opened 8 years ago

lelandrichardson commented 8 years ago

Several people have reached out to me regarding integration testing with Enzyme. While Enzyme is currently intended only for "Unit" Testing, I think that there is significant value to be extracted from Enzyme's API that could be used in the context of an integration test.

I'm not sure what this looks like yet. We could integrate with another (mature) integration testing framework, or we could build one ourselves and include it in this project, or we could build another project that depends on enzyme, or {insert your idea here}.

I'm creating this issue as an initial discussion grounds for people that are interested in this, and would like to discuss or propose ideas.

ljharb commented 8 years ago

Anything using mount is already an integration test - since "integration test" has lots of meanings, can you clarify what this means in the context of enyzme?

lelandrichardson commented 8 years ago

@ljharb what I mean is something closer to selenium / capybara.

Essentially, there could be a way that you point your test driver to a web-based endpoint that tests your full stack, and then you get a page with 0-many react root nodes (running along with any other JS you're including on the page). You'd be able to utilize enzyme or an enzyme-like API to target specific nodes/components on the page, and execute more complex behaviors (such as authenticating through an API, etc).

CrshOverride commented 8 years ago

It doesn't look like there's much activity here. I actually just started evaluating enzyme for integration testing. I've successfully rendered our application by mounting the top-level component along, our Redux infrastructure, as well as mocking all of the necessary API requests.

The initial render of the application was fairly easy. The only hitch was having to roll my own pause method to wait for any async API calls to complete before checking the component.

I'm currently stuck when trying to set input values in the UI. I can see that my event handlers are being triggered, but it seems that something in the Flux loops is causing an error. I'm getting the following error:

Invariant Violation: removeComponentAsRefFrom(...): Only a ReactOwner can have refs. You might be removing a ref to a component that was not created inside a component's `render` method, or you have multiple copies of React loaded (details: https://fb.me/react-refs-must-have-owner).

I've verified that I only have a single instance of React included on the page but I'm not sure what else would be causing that error (it obviously doesn't happen in the app itself).

joncursi commented 8 years ago

+1 for Capybara-like capabilities

pretentiousgit commented 8 years ago

I'm also stuck trying to set input values in the UI and seeing how they mutate. +1 for being able to fetch and set values such that it is similar to really typing into the input.

scottmas commented 7 years ago

This is a great idea. Imagine if enzyme supported jsdom integration tests out of the box. Since we'd be using jsdom, it would be super light weight and the tests could run on N virtual threads. This would massively parallellize integration testing in a way that just isn't possible with with full browser based integration tests, since browsers are too heavy on resource usage.

In the real world, integration tests are best at finding bugs where the shape of the data changes in the API's you're using, so a jsdom based integration tests using Enzyme full rendering should be both effective in catching regressions and being extremely fast to run. This seems like a big win to me.

Would there be any interest in creating a framework along these lines, either to be included in Enzyme or as a sister project? Using Enzyme as the test framework, jsdom as the test environment, and enabling parallel testing by default? I could take a stab at it

cannoneyed commented 7 years ago

Yeah, I'm very much in favor of pushing this forward as well - I've gotten pretty far with this but am finding there's going to be limitations because I can't access the instance() methods of a mounted component's children...

Here's an extremely contrived gist of how I'm currently approaching the idea of using enzyme and jest to "integration test" connected components:

// ConnectedComponent.js
class ConnectedComponent extends Component {
    fetchData = () => {
        this.props.fetchName()
    }

    render() {
        return (
            <h1>{ this.props.name }</h1>
    }
}

const mapStateToProps = (state) => {
    name: state.name,
}

const mapDispatchToProps = (dispatch) => {
    fetchName: () => dispatch(asyncActions.fetchName())
}

export default connect(mapStateToProps, mapDispatchToProps)(ConnectedComponent)

// test-utils.js
import { Provider } from 'react-redux'
import configureTestStore from 'shared/configure-store/test'

const Wrap = (Wrapped) => {
    const store = configureTestStore()
    return (
        <Provider store={ store }>
            <Wrapped />
        </Provider>
    )
}

// ConnectedComponent.test.js
describe('integration tests', () => {
    it('tests all the redux things', async () => {
        const Wrapped = mount(Wrap(ConnectedComponent)).find('ConnectedComponent')

        expect(Wrapped.props().name).toBeUndefined()

        await Wrapped.props().fetchName()
        // Ideally, I'd like to do the following to test component methods as well
        // await Wrapped.instance().fetchData()

        expect(Wrapped.props().name).toBeDefined()
    })
})
davidkell commented 7 years ago

The initial render of the application was fairly easy. The only hitch was having to roll my own pause method to wait for any async API calls to complete before checking the component.

@CrshOverride We are trying to implement a similar setup, how did you solve this pause issue? We do not have a direct handle on the function call (such as in @cannoneyed example above)

pudgereyem commented 7 years ago

I'm also interested in using enzyme to call Component methods instead of sending events when doing Integration (end to end) tests. I've found this blog post written by Class Dojo; https://engineering.classdojo.com/blog/2017/01/12/integration-testing-react-redux/

davidkell commented 7 years ago

After trawling through npm, we found this library: https://github.com/mlawrie/hereafter

The philosophy is pretty much what this thread is looking for:

"Hereafter lets you write concise, high-level, functional tests that provide similar coverage to your Selenium/Webdriver/Nightwatch/etc tests but execute hundreds or even thousands of times faster.

At the momement, Hereafter works with Mocha, Chai and Jest. I'm planning to add Jasime support, as well.

Hereafter defers evaluation of each line in your tests until the promises and callbacks triggered by previous lines have completed. In essence, it lets you ignore the fact that your code is asynchronous and write tests as though it were purely synchronous code.

Behind the scenes, Hereafter implements a similar polling scheme for expectations that you will find implemented in every functional testing tool. The key difference is that the polling happens much, much faster since there is far less overhead given that there is no web browser or DOM."

It works great for us and deserves many more stars !

martinp999 commented 7 years ago

@davidkell , not sure how it can be an integration test when you're not running in a browser?

davidkell commented 7 years ago

See the example on the main github page https://github.com/mlawrie/hereafter They use the enzyme mount method, which renders the React component with jsdom. Not sure why they say "no web browser or DOM", since jsdom is a DOM. I guess the point is that jsdom is so much more lightweight than 'real'/headless browsers.

martinp999 commented 7 years ago

@davidkell, thanks for the response. Any thoughts on using this approach vs real browsers? My concern is that it won't catch problems if, for instance, you had to support IE9 (not to mention latest Chrome, FF and Safari).

davidkell commented 7 years ago

@martinp999 Does enzyme support real browsers or just jsdom? But yes, indeed, it would never be enough to run the tests on jsdom for those reasons.

dschinkel commented 7 years ago

the majority if your tests should be unit tests, not integration tests anyway. If you're only relying on a bunch of integration tests (ice cream cone) you're doing it wrong.

However I do agree there is value in being able to create an integration test in certain cases that tests a particular component. For example lets say you're hooking up React-redux for the first time and trying to get a real network request working via an API call from an action creator, it's useful to also create an integration test as a means to run your connected container component in order to start running and debugging your implementation that's trying to call a REST Service API endpoint just as an example.

In this case for me, this time around I'm trying to create another kind of integration test. "integration test" doesn't mean "I'm testing more than one level deep via unit test", I'm referring to "hey I want to integration test this component to make sure that real API call over the network works for the first time since this is the first time I'm trying to use superagent calls in Redux" for example.

dschinkel commented 7 years ago

https://engineering.classdojo.com/blog/2017/01/12/integration-testing-react-redux/

dschinkel commented 7 years ago

I'm trying to understand why anyone needs to hit a real web server or use something like Casper or Selenium when you can create Integration tests using Mount() against the jsdom in order run your components and to have them make real calls to things OR integration tests that test that several "units" work together as expected. If you mount() a container or component, you can assert different things by diving down into those components since mount() renders the implementation of all children of that component. So why would anyone want to be writing casper, phantom, or selenium tests when you can use simulate click and enzyme's mount most the time for integration testing.

I mean I guess you could add tests that run over a real browser with stuff like casper but I wonder about the law of diminishing returns at that point. If you have QA and they're good at poking and prodding at the features, they should find those harder edge cases that the integration tests and unit tests did not find.

ljharb commented 7 years ago

@dschinkel because at some point you want your tests to exercise the actual backend, in an actual browser. Certainly you can get pretty thorough coverage without e2e tests; that doesn't mean they have no value.

dschinkel commented 7 years ago

Yea I get the need to test real API calls.

I was more wondering what you'd be missing after you did a bunch of integration tests using enzyme's mount() for testing things with simulate click, router transitions, and state transitions using jsdom. If you did all those tests without something like Casper (and a real web server + reaal browser) and did most of those types of tests with enzyme & mount, then I'm just wondering what testing over the real browser with additional tests such as casper tests would give me

because personally I'd prefer to be making a bunch of enzyme integration tests first, less hassle, no need for real things to run. I'm trying to find the value of something like adding casper or selenium tests after all that. And I don't see it.

I am asking because I'n not an integration test Guru (nor do I really wanna be because I value unit test more (test pyramid) :P), I'm more a Guru with TDD and isolated unit tests.

dschinkel commented 7 years ago

I'm also trying to determine if Integration Tests are a Scam :). Great talk by JB which I've had the honor of talking to and get advice from at times.

carlows commented 7 years ago

I'm wondering the same as @dschinkel, other than the point of connecting the real backend (which you can also do with the same setup in enzyme) what would be the benefit of using webdriver for integration tests? They run much much slower and as far as I can see you can do the same kind of user flow testing with enzyme

dschinkel commented 7 years ago

Yea I tend to agree @Carlows you can cover most of the Integration and isolated unit tests with mocha + enzyme or Jest + enzyme or whatever + enzyme. QA can find there rest and hard edge cases.

dschinkel commented 7 years ago

One of my favs, I know Eric

https://vimeo.com/102329098?lipi=urn%3Ali%3Apage%3Ad_flagship3_feed%3B34mZlR92RzKorZMKEyAbdA%3D%3D

octalmage commented 7 years ago

We're using Nightwatch for integration/acceptance tests and Enzyme for the React unit tests. We also need to test the stack used to render the React app (WordPress) so this isn't something we can test with Enzyme and the virtual dom alone.

Something I was thinking about is how great it is to be able to just include the component and find it using Enzyme. If that location strategy could be used in Selenium tests it would make managing selectors much easier.

DianaSuvorova commented 7 years ago

I think one can do this already. I mean writing selenium-like tests with enzyme.

imagine your index.js looks like below:

ReactDOM.render(
    <App />
  document.getElementById('root')
);

Now instead of rendering App directly into dom you use enzyme wrapper :

    const el = Enzyme.mount(<App />,
      { attachTo: document.getElementById('root') }
    );

In this case one can programmatically script e2e tests for example with puppeteer and jest:

test('can access enzyme el for e2e tests', async () => {
  const browser = await puppeteer.launch({});
  const page = await browser.newPage();

  await page.goto('http://localhost:3000', {waitUntil: 'networkidle2'});

  const appName = await page.evaluate(() => {
    app = window.el.findWhere(c => c.name() === 'App');
    return app.name();
  });
  expect(appName).toEqual('App');
  await browser.close();
});

https://github.com/DianaSuvorova/e2e

octalmage commented 7 years ago

For our integration tests we need to test more than just React components. We're using React in a WordPress plugin, so we want to test that WordPress plugin as well, and the integration between the two. Additionally in Enzyme we're passing data to the React components as props, so it's all mocked, WordPress makes the HTTP requests so to do a full e2e we need more than just mounting.

I looked into this a bit before, and this is how far I got:

https://gist.github.com/octalmage/1f40c00ae62b662eced63c9df6a5159c

Ignore the unique selectors piece, that was trying to use Enzyme to find components then use them in Nightwatch.

The goal is to be able to identify React components using their component name on a page (any page), then click and modify them. This got me pretty close.

DianaSuvorova commented 7 years ago

@octalmage, I am not sure what you mean by "more than just mounting". enzyme mount actually calls ReactDOM.render function. So with enzyme mounting you get more than with just react rendering.

octalmage commented 7 years ago

I'm talking about testing more than just a React application. So also testing the application that hosts the React components. ReactDOM.render wouldn't work for this either.

octalmage commented 7 years ago

Sorry, your solution might work actually for finding the actual dom element. I'll check it out!

octalmage commented 7 years ago

Ah I remember where this broke down for my needs. Lets say you're smoke testing a deployed staging application, so you need to load an existing url/page in a browser and test it. It would be great to be able to say, find the component "ListTabs" on the page and click it. Currently we use data-test-id attributes and selectors, but it would be great to not have to do that.

With your solution, the element returned from mounting wouldn't match the actual element on the page, so you wouldn't be able to use it as a selector.

DianaSuvorova commented 7 years ago

Some-weeks later update:

I did a write up here https://github.com/DianaSuvorova/e2e. On how to set up e2e test with enzyme and puppeteer