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

Created on 13 Jul 2016  路  12Comments  路  Source: facebook/relay

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.

All 12 comments

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..

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.

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?

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?

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

@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?

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.

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.

@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!

@GrigoryPtashko have u some progress on this?

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

@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 https://github.com/facebook/jest/issues/1898. Guys from the Jest team do not answer it unfortunately.

@GrigoryPtashko u should write a blog post about it

Was this page helpful?
0 / 5 - 0 ratings