Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration testing of Relay containers with Jest against a working GraphQL backend not working #1281

Closed
GrigoryPtashko opened this issue Jul 13, 2016 · 12 comments

Comments

@GrigoryPtashko
Copy link

GrigoryPtashko commented Jul 13, 2016

I'd like to implement the integration testing of my Relay containers against a running GraphQL backend server. I'm going to use Jest for this. I'd like to say that unit testing of React components works well as expected with my Jest setup.
Here's what I have in the package.json for the Jest:

"jest": {
    "moduleFileExtensions": [
      "js",
      "jsx"
    ],
    "moduleDirectories": [
      "node_modules",
      "src"
    ],
    "moduleNameMapper": {
      "^.+\\.(css|less)$": "<rootDir>/src/styleMock.js",
      "^.+\\.(gif|ttf|eot|svg|png)$": "<rootDir>/src/fileMock.js"
    },
    "unmockedModulePathPatterns": [
      "<rootDir>/node_modules/react/",
      "<rootDir>/node_modules/react-dom/",
      "<rootDir>/node_modules/react-addons-test-utils/",
      "<rootDir>/node_modules/react-relay/"
    ]
}

Here's the .babelrc I'm using:

{
  "presets": ["es2015", "react", "stage-0"],
  "plugins": ["./babelRelayPlugin.js"]
}

Here's the test itself. It must make a request to `http://localhost:10000/q' GraphQL endpoint to fetch a simple piece that represents the info about the current user ('me').

jest.disableAutomock();

import React from 'react';
import Relay from 'react-relay';
import TestUtils from 'react-addons-test-utils';
import RelayNetworkDebug from 'react-relay/lib/RelayNetworkDebug';

RelayNetworkDebug.init();
Relay.injectNetworkLayer(
  new Relay.DefaultNetworkLayer('http://localhost:10000/q')
);

describe('Me', () => {
  it('can make request to /q anyway', () => {
    class RootRoute extends Relay.Route {
      static queries = {
        root: (Component) => Relay.QL`
            query {
                root {
                    ${Component.getFragment('root')}
                }
            }
        `,
      };

      static routeName = 'RootRoute';
    }

    class AppRoot extends React.Component {
      static propTypes = {
        root: React.PropTypes.object,
      };

      render() {
        expect(this.props.root).not.toBe(null);
        expect(this.props.root.me).not.toBe(null);
        expect(this.props.root.me.firstName).not.toBe(null);
        expect(this.props.root.me.authorities[0]).not.toBe(null);
        expect(this.props.root.me.authorities[0].authority).toEqual('ROLE_ANONYMOUS_AAA');

        return (
          <div>
            {this.props.root.me.firstName}
          </div>
        );
      }
    }

    const AppContainer = Relay.createContainer(AppRoot, {
      fragments: {
        root: () => Relay.QL`
            fragment on Root {
                me {
                    firstName
                    email
                    authorities {
                        authority
                    }
                }
            }
        `,
      },
    });

    const container = TestUtils.renderIntoDocument(
      <div>
        <Relay.Renderer
          Container={AppContainer}
          queryConfig={new RootRoute()}
          environment={Relay.Store}
          render={({ done, error, props, retry, stale }) => {
            console.log('render: done', done);
            console.log('render: retry', retry);
            console.log('render: stale', stale);

            if (error) {
              console.log('render: error', error);

              return <div>error</div>;
            } else if (props) {
              console.log('render: props', props);

              return <AppContainer {...props} />;
            } else {
              console.log('render: loading');

              return <div>loading</div>;
            }
          }}
          forceFetch={true}
        />
      </div>
    );

    expect(container).not.toBe(null);
  });
});

The problem is that the test passes. But in my opinion it must fail at this line inside the render() expect(this.props.root.me.authorities[0].authority).toEqual('ROLE_ANONYMOUS_AAA');. It seems like the render() method is not executed at all.

I'm running Jest like this

./node_modules/.bin/jest

Does this all suppose to work at all?

Thank you.

@GrigoryPtashko
Copy link
Author

Hello guys!
By no means I want to disturb the Relay team with such newbie questions but is there any chance that someone takes a look at this one? It's kind of a stopper for me..

@josephsavona
Copy link
Contributor

At a quick glance nothing stands out as being obviously incorrect about the test setup; I'd expect it to fetch data. We'll need more information to help debug. Is render called? Is your network layer called (does it even make the HTTP request?). I would try putting breakpoints/log statements in RelayRenderer to make sure that it's even executing. It sounds like something is going weird and a component is mocked by Jest even though you don't expect it to be.

@GrigoryPtashko
Copy link
Author

GrigoryPtashko commented Jul 19, 2016

I found out that there was an exception during the fetching (sorry, I'm kind of new to the node debugging I had to do this first..):

TypeError: require(...) is not a function
    at RelayQueryRequest.getQueryString (project_dir/node_modules/react-relay/lib/RelayQueryRequest.js:93:50)
    at RelayDefaultNetworkLayer._sendQuery (project_dir/node_modules/react-relay/lib/RelayDefaultNetworkLayer.js:131:24)
    at eval (project_dir/node_modules/react-relay/lib/RelayDefaultNetworkLayer.js:60:20)
    at Array.map (native)
    at RelayDefaultNetworkLayer.sendQueries (project_dir/node_modules/react-relay/lib/RelayDefaultNetworkLayer.js:59:46)
    at RelayNetworkLayer.sendQueries (project_dir/node_modules/react-relay/lib/RelayNetworkLayer.js:94:34)
    at RelayNetworkLayer.instrumentedCallback [as sendQueries] (project_dir/node_modules/react-relay/lib/RelayProfiler.js:131:40)
    at eval (project_dir/node_modules/react-relay/lib/RelayNetworkLayer.js:129:16)
    at tryCallOne (project_dir/node_modules/promise/lib/core.js:37:12)
    at eval (project_dir/node_modules/promise/lib/core.js:123:15)
    at flush (project_dir/node_modules/asap/raw.js:50:29)
    at _timerAPIs.nextTick._timerAPIs.nextTick (project_dir/node_modules/jest-util/build/FakeTimers.js:368:18)
    at _combinedTickCallback (internal/process/next_tick.js:67:7)
    at process._tickCallback (internal/process/next_tick.js:98:9)

It's this line in the RelayQueryRequest in the source code.
Here I am not sure to which this is related - to Relay or to Jest?

@josephsavona
Copy link
Contributor

Hmm. printRelayQuery is a module that is shimmed to use either printRelayOSSQuery (in open source) or an internal version at Facebook. I suspect that the require is failing because Jest doesn't think that printRelayQuery is a module and so it is getting transformed differently.

@cpojer any ideas?

@cpojer
Copy link
Contributor

cpojer commented Jul 20, 2016

The "moduleNameMapper" config object can be used to map from one module to another, if that is the case.

@GrigoryPtashko
Copy link
Author

GrigoryPtashko commented Jul 21, 2016

@josephsavona

After adding the fbjs preset to babel I've managed to get printedQuery to work but
there is one "but"..
Long story short: I've got a strange behaviour of Jest when it is lanuched
with and without test path reg ex. My setup is very simple: the __tests__
folder contains two tests - SignIn-test.js and Me-test.js (in
SignIn-test.js I changed describe to xdescribe).

Here's the output when I run Jest without test path reg ex:

macbook@terminalx ~/Documents/work/project/src/main/frontend (5755)*$ NODE_ENV=test node ./node_modules/jest-cli/bin/jest.js --no-cache 
Using Jest CLI v13.2.3, jasmine2
 PASS  __tests__/SignIn-test.js (1.817s)
Running 1 test suite...
in printedQuery
printedQuery { text: 'query Me {\n  root {\n    ...F0\n  }\n}\nfragment F0 on Root {\n  me {\n    firstName,\n    email,\n    authorities {\n      authority\n    },\n    id\n  },\n  id\n}',
  variables: {} }
 PASS  __tests__/Me-test.js (2.345s)
1 test skipped, 1 test passed (2 total in 2 test suites, run time 4.074s)

As you see the printQuery is executed as expected.

And here's the output when I specify the test path reg ex to execute only the Me-test.js:

macbook@terminalx ~/Documents/work/project/src/main/frontend (5755)*$ NODE_ENV=test node ./node_modules/jest-cli/bin/jest.js --no-cache Me
Using Jest CLI v13.2.3, jasmine2
 PASS  __tests__/Me-test.js (1.802s)
1 test passed (1 total in 1 test suite, run time 2.815s)

    in printedQuery
    TypeError: require(...) is not a function
        at RelayQueryRequest.getQueryString (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/react-relay/lib/RelayQueryRequest.js:94:50)
        at RelayDefaultNetworkLayer._sendQuery (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/react-relay/lib/RelayDefaultNetworkLayer.js:131:24)
        at eval (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/react-relay/lib/RelayDefaultNetworkLayer.js:60:20)
        at Array.map (native)
        at RelayDefaultNetworkLayer.sendQueries (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/react-relay/lib/RelayDefaultNetworkLayer.js:59:46)
        at RelayNetworkLayer.sendQueries (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/react-relay/lib/RelayNetworkLayer.js:94:34)
        at RelayNetworkLayer.instrumentedCallback [as sendQueries] (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/react-relay/lib/RelayProfiler.js:131:40)
        at eval (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/react-relay/lib/RelayNetworkLayer.js:129:16)
        at tryCallOne (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/promise/lib/core.js:37:12)
        at eval (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/promise/lib/core.js:124:15)
        at flush (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/asap/raw.js:50:29)
        at _timerAPIs.nextTick._timerAPIs.nextTick (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/jest-util/build/FakeTimers.js:368:18)
        at _combinedTickCallback (internal/process/next_tick.js:67:7)
        at process._tickCallback (internal/process/next_tick.js:98:9)
    TypeError: require(...) is not a function
        at eval (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/react-relay/lib/GraphQLQueryRunner.js:206:48)
        at tryCallOne (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/promise/lib/core.js:37:12)
        at eval (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/promise/lib/core.js:124:15)
        at flush (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/asap/raw.js:50:29)
        at _timerAPIs.nextTick._timerAPIs.nextTick (/Users/macbook/Documents/work/project/src/main/frontend/node_modules/jest-util/build/FakeTimers.js:368:18)
        at _combinedTickCallback (internal/process/next_tick.js:67:7)
        at process._tickCallback (internal/process/next_tick.js:98:9)

See? Why? What changes inside Jest that causes the exception?

It is this line in transpiled code (I've added the console.logs):

  RelayQueryRequest.prototype.getQueryString = function getQueryString() {
    var printedQuery = this._printedQuery;
    if (!printedQuery) {
      console.log('\nin printedQuery');
      printedQuery = require('./printRelayQuery')(this._query); // <-- this one throws **the** exception
      console.log('printedQuery', printedQuery);
      this._printedQuery = printedQuery;
    }
    return printedQuery.text;
  };

To make things worse I can tell you that the same behaviour as in the second case I see when
I launch Jest with the -i (--runInBand) switch.

PS I also removed babel-jest from dev dependencies and specified my own preprocessor:

const babel = require('babel-core');
const jestPreset = require('babel-preset-jest');
const fbjsPreset = require('babel-preset-fbjs/configure')({
  stripDEV: false,
});

module.exports = {
  process(src, filename) {
    if (babel.util.canCompile(filename)) {
      return babel.transform(src, {
        filename,
        presets: [
          jestPreset,
          fbjsPreset,
        ],
        retainLines: true,
      }).code;
    }
    return src;
  },
};

Any ideas?

@josephsavona
Copy link
Contributor

That's really strange. The fact that it works on one of the tests seems important. Is there anything different about the two tests that could be causing Jest to behave differently?

I'm going to close this since it doesn't appear to be a Relay issue per-se, but feel free to continue discussion here. You may also want to file an issue on http://github.com/facebook/jest so that more Jests experts can see it and help out.

@GrigoryPtashko
Copy link
Author

GrigoryPtashko commented Jul 21, 2016

I don't change any test between these two runs. I even specify the --no-cache flag just to be sure that Jest doesn't take anything from the cache.

The first test SignIn-test.js is a simple React component test. The other one - Me-test.js - I showed in the very first post. Since I started this topic nothing changed. I'll show the second test just for the sake of fullness of the info from my side.

jest.disableAutomock();

import React from 'react';
import ReactDom from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import SignIn from '../src/components/SignIn';

xdescribe('SignIn', () => {
  it('shows the social login buttons', () => {
    const signin = TestUtils.renderIntoDocument(
      <div><SignIn /></div>
    );
    expect(signin).not.toBe(null);

    const signinNode = ReactDom.findDOMNode(signin);

    expect(signinNode).not.toBe(null);
    expect(signinNode.textContent).toMatch(/Sign In with Facebook/);
  });
});

Here SignIn is the stateless React component. So, answering your question I don't by myself change anything between the test runs. It's really a mystery to me too.

PS I'll file an issue to the Jest repo.

@GrigoryPtashko
Copy link
Author

GrigoryPtashko commented Aug 5, 2016

@josephsavona I finally got some results but there're not solving the issue tho. I'm just asking for an advice. Long story short is that I could not debug my test because jest has some kind of issue when the OSS Relay is built with inlineRequires set to true. But thanks to this topic jest#1321 (listening to all of the updates to Relay's repo turned out to be a great source of information) I found out that I can build Relay with different options. I've re-built it with inlineRequires set to false and was able to run a single test Me-test.js with jest.

And here's what I've got at the moment. Relay makes the query!! I can see it in my backend server log. But. The render() is never get called. And here's why. In this Me-test.js in the Relay.Renderer in the render callback the done arg is always false and props is always null. I see that Relay makes exactly 3 tries and then quits. So my AppContainer is never get created and thus its render() method is never get called.

Can you give my some further direction on how can I debug this?

Thanks!

@sibelius
Copy link
Contributor

@GrigoryPtashko have u some progress on this?

could u share a repo or a gist with ur current solution?

@GrigoryPtashko
Copy link
Author

@sibelius @lucasbento sorry for keeping silence. My macbook was in the service. I'm glad I'm not alone in this question.. I guess you've already guessed that I have a working solution for Jest 14 but not for Jest >= 15. Here's the issue with the repro and the example for Jest 14 jestjs/jest#1898. Guys from the Jest team do not answer it unfortunately.

@sibelius
Copy link
Contributor

sibelius commented Nov 7, 2016

@GrigoryPtashko u should write a blog post about it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants