Categories
Testing

Introduction to Testing with Jest

With apps being as complex as they are today, we need some way to verify that our changes to apps didn’t cause regressions. To do this, we need unit tests.

We can add unit tests to JavaScript apps easily with the Jest test runner. It’s very easy to get running and we can write lots of tests with it that runs quickly.

In this article, we’ll look at how to set up Jest from scratch and write some tests with it. Both synchronous an asynchronous functions will be tested.

Getting Started

To get started, we simply run a few commands. In our app’s project folder, we run:

yarn add --dev jest

or

npm install --save-dev jest

to add Jest to our JavaScript apps.

The example we have below will have 2 simple scripts each containing a module that has several functions.

First we create the code that we’re testing. We’ll test both code that does and doesn’t call APIs.

We create example.js and put in the following code:

const add = (a, b) => a + b;
const identity = a => a;
const deepCopy = (obj, copiedObj) => {
    if (!copiedObj) {
        copiedObj = {};
    }
    for (let prop of Object.keys(obj)) {
        copiedObj = {
            ...copiedObj,
            ...obj
        };
        if (typeof obj[prop] === 'object' && !copiedObj[prop]) {
            copiedObj = {
                ...copiedObj,
                ...obj[prop]
            };
            deepCopy(obj[prop], copiedObj);
        }
    }
    return copiedObj;
}
module.exports = {
    add,
    identity,
    deepCopy
}

The code above has functions to add numbers and a function that returns the same thing that it passes in.

Then we create request.js with the following:

const fetchJokes = async () => {
    const response = await fetch('[http://api.icndb.com/jokes/random/'](http://api.icndb.com/jokes/random/%27));
    return response.json();
};
module.exports = {
    fetchJokes
}

The code above gets random jokes from the Chuck Norris Jokes API.

Writing Tests for Synchronous Code

Now that we have the code that we want to test, we’re ready to create some test code for them.

First we create tests for the functions in example.js.

We add the following tests:

const { add, identity } = require('./example');

test('adds 1 + 2 to equal 3', () => {
    expect(add(1, 2)).toBe(3);
});

test('adds 1 + 2 is truthy', () => {
    const sum = add(1, 2);
    expect(sum).toBeTruthy();
});

test('adds 1 + 2 to be defined', () => {
    const sum = add(1, 2);
    expect(sum).not.toBeUndefined();
    expect(sum).toBeDefined();
});

test('identity(null) to be falsy', () => {
    expect(identity(null)).toBeFalsy();
    expect(identity(null)).not.toBeTruthy();
});

The code above is very simple. It imports the functions from example.js and runs tests with it.

The first test calls add from example.js by passing in 2 numbers and checking that the returned result is what we expect.

The 2 tests below it are very similar except that we use different matcher functions to check the results.

The last test runs the identity function with null and they use the toBeFalsy and not.toBeTruthy matchers to check that null is indeed falsy.

In summary, Jest has the following matchers:

  • toBeNull — matches only null
  • toBeUndefined — matches only undefined
  • toBeDefined — is the opposite of toBeUndefined
  • toBeTruthy — matches anything truthy
  • toBeFalsy — matches anything falsy
  • toBeGreaterThan — check if a value is bigger than what we expect
  • toBeGreaterThanOrEqual — check if a value is bigger than or equal to what we expect
  • toBeLessThan — check if a value is less than what we expect
  • toBeLessThanOrEqual — check if a value is less than or equal what we expect
  • toBe — check if a value is the same using Object.is to compare the values
  • toEqual — checks every field recursively of 2 objects to see if they’re the same
  • toBeCloseTo — check for floating point equality of values.
  • toMatch — check if a string matches a give regex
  • toContain — check if an array has the given value
  • toThrow — check if an exception is thrown

The full list of expect methods are here.

We can use some of them as follows by adding them to example.test.js:

test('but there is a "foo" in foobar', () => {
    expect('foobar').toMatch(/foo/);
});

test(
    'deep copies an object and returns an object with the same properties and values',
    () => {
        const obj = {
            foo: {
                bar: {
                    bar: 1
                },
                a: 2
            }
        };
        expect(deepCopy(obj)).toEqual(obj);
    }
);

In the code above, we use the toMatch matcher to check if 'foobar' has the 'foo' substring in the first test.

In the second test, we run our deepCopy function that we added earlier and test if it actually copies the structure of an object recursively with the toEqual matcher.

toEqual is very handy since it checks for deep equality by inspecting everything in the object recursively rather than just for reference equality.

Writing Tests for Asynchronous and HTTP Request Code

Writing tests for HTTP tests requires some thinking. We want the tests to run anywhere with or without an internet connection. Also, the test shouldn’t depend on the results of the live server since it’s supposed to be self-contained.

This means that we have to mock the HTTP request rather than calling our code directly.

To test our fetchJokes function in request.js, we have to mock the function.

To do this, we create a __mocks__ folder in our project folder and in it, create requests.js. The file name should always match the filename with the code that we’re mocking.

We can mock it as follows:

const fetchJokes = async () => {
    const mockResponse = {
        "type": "success",
        "value": {
            "id": 407,
            "joke": "Chuck Norris originally wrote the first dictionary. The definition for each word is as follows - A swift roundhouse kick to the face.",
            "categories": [

]
        }
    };
    return Promise.resolve(mockResponse);
};
module.exports = {
    fetchJokes
}

Both the real and mock fetchJokes function returns a promise. The real function returns a promise from the fetch function while the mock one calls Promise.resolve directly.

This means that the real one actually makes the GET request and the mock tests resolve to the response that we want to test for.

Then in example.test.js, we add:

jest.mock('./request');
const { fetchJokes } = require('./request');

to the top of the file and:

test('jokes to be fetched', async () => {
    const mockResponse = {
        "type": "success",
        "value": {
            "id": 407,
            "joke": "Chuck Norris originally wrote the first dictionary. The definition for each word is as follows - A swift roundhouse kick to the face.",
            "categories": [

]
        }
    };
    await expect(fetchJokes()).resolves.toEqual(mockResponse)
});

to the bottom of it.

The test above just call the mock fetchJokes function since we have:

jest.mock('./request');

However, we still need to import the real one since the real one is replaced with the mock one when the test runs.

resolves will resolve the promise returned from the mock fetchJokes function and toEqual will check the structure of the response from the promise’s resolved value.

Finally, in the scripts section of package.json , we put:

"test": "jest"

so we can run npm test to run the tests.

Then when we run npm test , we should get:

PASS  ./example.test.js
  √ adds 1 + 2 to equal 3 (2ms)
  √ adds 1 + 2 is truthy
  √ adds 1 + 2 to be defined (1ms)
  √ identity(null) to be falsy
  √ but there is a "foo" in foobar
  √ deep copies an object and returns an object with the same properties and values (1ms)
  √ jokes to be fetched (1ms)

Test Suites: 1 passed, 1 total
Tests:       7 passed, 7 total
Snapshots:   0 total
Time:        1.812s, estimated 2s
Ran all test suites.

No matter how and when we run the tests, they should still pass since they’re independent of each other and don’t rely on external network requests.

Conclusion

When we write unit tests, we have to make sure each test is independent from each other and also should make any network requests.

Jest makes testing easy by letting mock things like code that makes HTTP requests easily.

It’s also easy to set up, and has lots of matchers for all kinds of tests.

Categories
HTML JavaScript

What’s new in Lighthouse 6

Lighthouse is a tool made by Google to let us measure the performance metrics of our browser apps.

Version 6 is the latest version of the software.

As with other versions, it’s available as part of Chrome or as an NPM package to let us run it automatically to check those performance metrics when the software is being built for continuous integration.

In this article, we’ll look at the latest features of Lighthouse 6.

New Metrics

Lighthouse 6 added some new metrics for measuring the performance of our browser apps.

Largest Contentful Paint (LCP)

One of them is the Largest Contentful Paint (LCP). It’s a measurement of perceived loading experience.

It captures how long it takes to load all the parts of our page.

Like with First Contentful Paint (FCP), we don’t want this to take too long.

If it’s more than 2.5 seconds, then it’s too long and we’ve to make our web app speedier.

Cumulative Layout Shift (CLS)

Another new metric is the Cumulative Layout Shift or CLS.

This is a measurement of visual stability.

Users don’t want a web page to have content that moves around too much.

Jumpy pages make it easy for users to do something they don’t want to do accidentally.

Therefore, any unexpected shift is annoying.

It’s measured by the following formula:

layout shift score = impact fraction * distance fraction

Impact fraction is the measurement of how unstable some element is.

It’s a percentage change of the distance from the original position to the new position.

Distance fraction is the distance that an element moved relative to the viewport.

It’s the great distance of any unstable element that has moved in the frame.

A score below 0.1 is considered good.

These are welcome changes since slowing loading and jumpy pages are always frustrating for users.

Total Blocking Time (TBT)

Total Blocking Time is another measurement of responsiveness.

It’s the total duration of when the main thread is blocked long enough to prevent input responsiveness.

It’s the total time between FCP and Time to Interactive.

Time to Interactive is the time to take to load the page until when we can interact with the page.

Performance Score Update

In Lighthouse 6, the performance score is calculated from the weighted blend of multiple performance metrics to summarize page speed.

The metrics are weighted as follows according to https://web.dev/lighthouse-whats-new-6.0/#new-metrics:

Phase Metric Name Metric Weight
Early (15%) First Contentful Paint (FCP) 15%
Mid (40%) Speed Index (SI) 15%
Largest Contentful Paint (LCP) 25%
Late (15%) Time To Interactive (TTI) 15%
Main Thread (25%) Total Blocking Time (TBT) 25%
Predictability (5%) Cumulative Layout Shift (CLS) 5%

The weights are changed in all the categories to takes into account the new metrics.

In version 5, the weights are as follows:

Phase Metric Name Metric Weight
Early (23%) First Contentful Paint (FCP) 23%
Mid (34%) Speed Index (SI) 27%
First Meaningful Paint (FMP) 7%
Finished (46%) Time to Interactive (TTI) 33%
First CPU Idle (FCI) 13%
Main Thread Max Potential FID 0%

TTI’s weight decreased according to user feedback. It’s very variable so reducing its weight will decrease its variability.

FCP is also decreased in weight since it doesn’t give us the full picture of loading.

The first CPU Idle metric is deprecated since it isn’t as direct as other metrics for interactivity.

With Lighthouse 6, we can use the Lighthouse Scoring Calculator to make our calculations.

New Audits Tools

Lighthouse 6 can audit unused JavaScript.

It’s included since 2017, but it’s disabled by default to keep Lighthouse fast.

Now that the tool is more efficient, it’s turned on by default.

It also has some new audit features to audit for some accessibility attributes.

They include:

  • aria-hidden-body
  • aria-hidden-focus
  • aria-input-field-name
  • aria-toggle-field-name
  • form-field-multiple-labels
  • heading-order
  • duplicate-id-active
  • duplicate-id-aria

aria-hidden-body makes sure aria-hidden=’true’ is in the body element

aria-hidden-focus checks for aria-hidden in focusable elements.

aria-input-field-name checks for aria attributes in input field names.

aria-toggle-field-name checks for aria attributes in a toggle field.

Form fields shouldn’t have multiple labels.

Headings should be sequentially descending order.

And there shouldn’t be any duplicate aria IDs.

Lighthouse 6 check for all those so that we won’t confuse users that are impaired.

Maskable icons are a new icon format that supports progressive web apps.

It’ll check for them so that we get good looking icons everywhere.

It also checks for the meta charset element to our HTML.

We also should add a Content-Type response header which it also checks for.

It specifies the character encoding explicitly so browsers won’t be confused.

With this declaration, browsers can’t render our page wrong.

Otherwise, it can render our page wrong and make the user experience poor.

Source Location Links

Lighthouse 6 provides us with the lines of code in our code that are causing audits to fail.

It has links to look through them in the Sources panel.

Experimental Features

Experimental features in Lighthouse 6 that are worth looking at include the ability to detect duplicate modules in JavaScript bundles.

It can also detect extra polyfills that we don’t need in modern browsers.

And it can detect unused JavaScript and report them by modules.

There’s also a visualization of all things in a module that require changes to improve the audit score.

We can enable them with the --preset experimental switch.

For example, we can run:

lighthouse https://example.com --view --preset experimental

to run Lighthouse in the command line with the experimental features on.

Lighthouse CI

Lighthouse CI is a Node CLI program and a server that lets us run Lighthouse audits for every commit.

It can easily be integrated into the continuous integration pipeline.

It supports many CI providers like Travis, Circle, GitLab, and Github Actions.

Docker images are provided to let us make the work of setting up the build pipeline with Lighthouse easy.

We can also install it manually.

To do that, we run:

npm install -g lighthouse

Then we can run it by running:

lighthouse <url>

Where url is the URL of our page.

Then we can change some options like logging and other configuration.

Logging can be set with --verbose or --quiet.

Other configuration options include changing the port, host name, emulate desktop, mobile, etc.

The full list of options are at https://www.npmjs.com/package/lighthouse#using-lighthouse-in-chrome-devtools

For instance, we can run Lighthouse and output to JSON by running:

lighthouse --output json

Renamed Chrome DevTools Panel

The Audits panel has been renamed to the Lighthouse panel in chrome.

This makes it easier to find.

Mobile Emulation

We can test mobile performance with emulation of slow network and CPU conditions.

Device screen emulation can also be done.

Lighthouse 6 has changed the reference device to the Moto G4.

Browser Extension

There’s a Chrome extension for Lighthouse.

This lets us run it locally in addition to what’s in Chrome itself.

It uses the PageSpeed Insights API.

The API has many limitations.

It can’t audit non-public websites since it runs from a remote server and not locally.

It’s also not guaranteed to have the latest Lighthouse release.

In Chrome, we should use the Lighthouse section of the dev tools to do our audits.

Or we can use the Node package version.

Conclusion

Lighthouse 6 is an even better version of Lighthouse.

It has new metrics to audit the performance of an app from the user’s perspective.

The weights to calculate the final performance score is changed from user feedback.

Also, it can look through our code to find where the problems originate.

This is very helpful when it comes to fixing problems easily.

With the Node package, we can automate our auditing easily.

Then the problems that needed to be fixed will be apparent to us all the time.

Categories
Testing

Creating Automatic Jest Mocks

To create unit tests, we often have to mock functions to bypass various parts of the code. For example, if we don’t want to run code that calls an API in a function, then we have to create a mock of that function.

In this article, we’ll look at some ways to create mocks functions and classes in Jest tests.

ES6 Class Mocks

We can use Jest to create ES6 class mocks.

ES6 classes are just syntactic sugar on top of constructor functions, so we can use the same methods for mock functions to mock classes.

Making Something to Test

First, we make something to test by creating videoPlayer.js and add:

class VideoPlayer {
    constructor() {

    }

    playVideoFile(fileName) {
        console.log(`Playing ${fileName}`);
    }
}

module.exports = VideoPlayer;

Then we create app.js as follows:

const VideoPlayer = require('./videoPlayer');

class VideoPlayerApp {
    constructor() {
        this.videoPlayer = new VideoPlayer();
    }

    play() {
        const videoFileName = 'video.mp4';
        this.videoPlayer.playVideoFile(videoFileName);
    }
}

module.exports = VideoPlayerApp;

Creating Automatic Mocks

In this example, we’ll mock the VideoPlayer class.

To do this, we can call jest.mock(‘./videoPlayer’); since we export the VideoPlayer class with module.exports .

Jest is smart enough to create its own mock class in place of the actual VideoPlayer class in tests.

It’ll replace the class with a mock constructor function, and replaces all its methods with mock functions that always return undefined .

Method calls are saved in theAutomaticMock.mock.instances[index].methodName.mock.calls, where theAutomaticMock is replaced with the same name as the class we originally mocked.

In our example, we aren’t going to create our own implementation of the methods, so once we called jest.mock(‘./videoPlayer’); , we’re done with mocking the VideoPlayer class.

Then we can create app.test.js to put our tests. We add the following:

const VideoPlayer = require('./videoPlayer');
const VideoPlayerApp = require('./app');
jest.mock('./videoPlayer');

beforeEach(() => {
    VideoPlayer.mockClear();
});

test('VideoPlayer is called once', () => {
    const videoPlayerApp = new VideoPlayerApp();
    expect(VideoPlayer).toHaveBeenCalledTimes(1);
});

test('VideoPlayer is called with video.mp4', () => {
    expect(VideoPlayer).not.toHaveBeenCalled();
    const videoPlayerApp = new VideoPlayerApp();
    videoPlayerApp.play();
    const videoFileName = 'video.mp4';
    const mockVideoPlayer = VideoPlayer.mock.instances[0];
    const mockPlayVideoFile = mockVideoPlayer.playVideoFile;
    expect(mockPlayVideoFile.mock.calls[0][0]).toEqual(videoFileName);
    expect(mockPlayVideoFile).toHaveBeenCalledWith(videoFileName);
    expect(mockPlayVideoFile).toHaveBeenCalledTimes(1);
});

We important the modules for containing each class. The VideoPlayer class will be replaced with our mock, but we still have to import it.

Then we have our beforeEach hook which runs:

VideoPlayer.mockClear();

to clear the mocks of the VideoPlayer class.

Next, we write our first test, which is:

test('VideoPlayer is called once', () => {
    const videoPlayerApp = new VideoPlayerApp();
    expect(VideoPlayer).toHaveBeenCalledTimes(1);
}););

We create a new VideoPlayerApp instance. The constructor of the VideoPlayerApp calls runs new VideoPlayer(); , which means the VideoPlayer class is called.

Then we check that:

expect(VideoPlayer).toHaveBeenCalledTimes(1);

which should be true since the VideoPlayer constructor function, aka the class, is called once.

Next, we look at our second test, which is more complex:

test('VideoPlayer is called with video.mp4', () => {
    expect(VideoPlayer).not.toHaveBeenCalled();
    const videoPlayerApp = new VideoPlayerApp();
    videoPlayerApp.play();
    const videoFileName = 'video.mp4';
    const mockVideoPlayer = VideoPlayer.mock.instances[0];
    const mockPlayVideoFile = mockVideoPlayer.playVideoFile;
    expect(mockPlayVideoFile.mock.calls[0][0]).toEqual(videoFileName);
    expect(mockPlayVideoFile).toHaveBeenCalledWith(videoFileName);
    expect(mockPlayVideoFile).toHaveBeenCalledTimes(1);
});

First, we check that VideoPlayer hasn’t been called to make sure that VideoPlayer.mockClear() is working.

Then we create a new instance of VideoPlayerApp and call play on it as follows:

const videoPlayerApp = new VideoPlayerApp();
videoPlayerApp.play();

After that, we get our mock VideoPlayer instance by running:

const mockVideoPlayer = VideoPlayer.mock.instances[0];

Then we call the playVideoFile method of the VideoPlayer class that was mocked as follows:

const mockPlayVideoFile = mockVideoPlayer.playVideoFile;

Then we can get the first argument that’s passed into the mock playVideoFile call by using:

mockPlayVideoFile.mock.calls[0][0]

We can then check that the call was done with 'video.mp4' passed by writing:

expect(mockPlayVideoFile.mock.calls[0][0]).toEqual(videoFileName);

Equivalently, we can write:

expect(mockPlayVideoFile).toHaveBeenCalledWith(videoFileName);

Finally, we check that the playVideoFile method was called only once by running:

expect(mockPlayVideoFile).toHaveBeenCalledTimes(1);

This makes sense since playVideoFile is only called once in our test.

Photo by Andrew Tanglao on Unsplash

Running the Tests

Be sure to install Jest by running:

npm i jest

and put:

"test": "jest"

in the scripts section so we can run the tests.

In the end, we should get:

PASS  ./app.test.js
  √ VideoPlayer is called once (3ms)
  √ VideoPlayer is called with video.mp4 (2ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.067s
Ran all test suites.

Conclusion

With Jest’s automatic mocks, we can mock classes or constructor functions easily.

All methods are mocked with functions that return undefined.

Then we can retrieve the mock by using mockedObject.mock.instances, which is an array.

Then we can run checks as usual. with toEqual, toHaveBeenCalledWith, and other matcher methods.

Categories
GraphQL

React Tips — GraphQL Queries, URL Parameters and React Router

React is a popular library for creating web apps and mobile apps.

In this article, we’ll look at some tips for writing better React apps.

Make GraphQL Queries with React Apollo

We can make GraphQL queries with React Apollo by using the client.query method.

This is available once we update the component by calling withApollo function.

For instance, we can write:

class Foo extends React.Component {
  runQuery() {
    this.props.client.query({
      query: gql`...`,
      variables: {
        //...
      },
    });
  }

  render() {
    //...
  }
}

export default withApollo(Foo);

The client.query method will be available in the props once we call withApollo with our component to return a component.

The method takes an object that has the query and variables properties.

The query property takes a query object returned by the gql form tag.

variables has an object with variables that we interpolate in the query string.

We can also wrap our app with the ApolloConsumer component.

For example, we can write:

const App = () => (
  <ApolloConsumer>
    {client => {
      client.query({
        query: gql`...`,
        variables: {
          //...
        }
      })
    }}
  </ApolloConsumer>
)

We can make our query in here.

For function components, there’s the useApolloClient hook.

For instance, we can write:

const App = () => {
  const client = useApolloClient();
  //...
  const makeQuery = () => {
    client => {
      client.query({
        query: gql`...`,
        variables: {
          //...
        }
      })
    }
  }
  //...
}

The hook returns the client that we can use to make our query.

There’s also the useLazyQuery hook that we can use to make queries.

For instance, we can write:

const App = () => {
  const [runQuery, { called, loading, data }] = useLazyQuery(gql`...`)
  const handleClick = () => runQuery({
    variables: {
      //...
    }
  })
  //...
}

runQuery lets us make our query.

React Router Pass URL Parameters to Component

We can pass URL parameters if we create a route that allows us to pass parameters to it.

For instance, we can write:

const App = () => {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/foo">foo</Link>
          </li>
          <li>
            <Link to="/bar">bar</Link>
          </li>
          <li>
            <Link to="/baz">baz</Link>
          </li>
        </ul>

        <Switch>
          <Route path="/:id" children={<Child />} />
        </Switch>
      </div>
    </Router>
  );
}

function Child() {
  const { id } = useParams();

  return (
    <div>
      <h3>ID: {id}</h3>
    </div>
  );
}

We have a Child component that has the useParams hook.

It lets us get the URL parameters that we want and it’s passed in from navigation.

It returns the URL parameter as a key-value pair.

The keys are what we defined and the value is what we have passed when we navigate.

In App , we have the Link components with the paths.

And also we have the Switch components that have the route that takes the id URL parameter.

The Route has the route that we pass in. children has the component that’s displayed.

Preventing Form Submission in a React Component

There are several ways to prevent form submission in a React component.

If we have a button inside the form, we can make set the button’s type to be button.

For instance, we can write:

<button type="button" onClick={this.onTestClick}>click me</Button>

If we have a form submit handler function passed into the onSubmit prop;

<form onSubmit={this.onSubmit}>

then in th onSubmit method, we call preventDefault to stop the default submit behavior.

For example, we can write:

onSubmit (event) {
  event.preventDefault();
  //...
}

TypeScript Type for the Match Object

We can use the RouteComponentProps interface, which is a generic interface that provides some base members.

We can pass in our own interface to match more members if we choose.

For example, we can write:

import { BrowserRouter as Router, Route, RouteComponentProps } from 'react-router-dom';

interface MatchParams {
  name: string;
}

interface MatchProps extends RouteComponentProps<MatchParams> {}

const App = () => {
  return (
    <Switch>
      <Route
         path="/products/:name"
         render={({ match }: MatchProps) => (
            <Product name={match.params.name}
         /> )}
      />
    </Switch>
  );
}

We use the MatchProps interface that we created as the type for the props parameter in the render prop.

Then we can reference match.params as we wish.

Conclusion

We can make GraphQL queries in a React component with the React Apollo client.

React Router lets us pass in URL parameters easily.

It works with JavaScript and TypeScript.

We can prevent form submission with event.preventDefault() in the submit handler.

Categories
GraphQL

Constructing Types with the GraphQL Package

We can create a simple GraphQL server with Express. To do this, we need the express-graphql and graphql packages.

In this article, we’ll look at how to add types that we can use to build a schema with the graphql package.

Constructing Types

We can construct a schema programmatically with the GraphQLSchema constructor that comes with the graphql package.

Instead of defining Query and Mutation types using the schema language, we can create them as separate object types.

For example, we can write the following to create a type with the graphql.GraphQLObjectType constructor to create an object type programmatically:

const express = require('express');
const graphqlHTTP = require('express-graphql');
const graphql = require('graphql');
const userType = new graphql.GraphQLObjectType({
  name: 'User',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
  }
});

let users = {
  '1': {
    id: '1',
    name: 'Jane'
  }
}

const queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    user: {
      type: userType,
      args: {
        id: { type: graphql.GraphQLString }
      },
      resolve: (_, { id }) => {
        return users[id];
      }
    }
  }
});

const schema = new graphql.GraphQLSchema({ query: queryType });

const app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(3000, () => console.log('server started'));

In the code above, we created the userType GraphQL data type by writing:

const userType = new graphql.GraphQLObjectType({
  name: 'User',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
  }
});

The name field defines the name of our type and the fields object has the fields that we include with the type. We defined id and name both to have type String .

Then we define our Query type with:

const queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    user: {
      type: userType,
      args: {
        id: { type: graphql.GraphQLString }
      },
      resolve: (_, { id }) => {
        return users[id];
      }
    }
  }
});

In the code above, we defined the name of the type to be Query . The fields we include is the user field, which is of type User that we defined above.

Also, we specified that we have the String id as the argument with the args property.

Finally, we have a resolve property with the resolver to return what we want to return.

In this case, we want to return the User from the users object given the id passed into the argument.

Then when we make the following query:

{
  user(id: "1"){
    id
    name
  }
}

We get back:

{
  "data": {
    "user": {
      "id": "1",
      "name": "Jane"
    }
  }
}

since we have the following in the users object:

let users = {
  '1': {
    id: '1',
    name: 'Jane'
  }
}

We can do the same with mutations.

This is particularly useful is we want to create a GraphQL schema automatically from something else like a database schema. We may have a common format for something like creating and updating database records.

It’s also useful for implementing features like union types that don’t map to ES6 constructs.

GraphQLUnionType

We can create union types with the GraphQLUnionType constructor.

To create a union type and use it in our app, we can use the GraphQLUnionType constructor as follows:

const express = require('express');
const graphqlHTTP = require('express-graphql');
const graphql = require('graphql');
class Dog {
  constructor(id, name) {
    this.id = id;
    this.name = name;
  }
};

class Cat {
  constructor(id, name, age) {
    this.id = id;
    this.name = name;
    this.age = age;
  }
};

const DogType = new graphql.GraphQLObjectType({
  name: 'Dog',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
  }
});

const CatType = new graphql.GraphQLObjectType({
  name: 'Cat',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
    age: { type: graphql.GraphQLInt },
  }
});

const PetType = new graphql.GraphQLUnionType({
  name: 'Pet',
  types: [DogType, CatType],
  resolveType(value) {
    if (value instanceof Dog) {
      return DogType;
    }
    if (value instanceof Cat) {
      return CatType;
    }
  }
});

let pets = {
  '1': new Dog('1', 'Jane'),
  '2': new Cat('1', 'Jane', 11),
}

const queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    pet: {
      type: PetType,
      args: {
        id: { type: graphql.GraphQLString }
      },
      resolve: (_, { id }) => {
        return pets[id];
      }
    }
  }
});

const schema = new graphql.GraphQLSchema({ query: queryType });

const app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(3000, () => console.log('server started'));

In the code above, we created the Dog and Cat classes to serve as models for our data.

Then we create the GraphQL Dog and Cat types as follows:

const DogType = new graphql.GraphQLObjectType({
  name: 'Dog',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
  }
});
const CatType = new graphql.GraphQLObjectType({
  name: 'Cat',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
    age: { type: graphql.GraphQLInt },
  }
});

We defined the DogType and CatType constants to define the Dog and Cat object types.

Dog has id and name fields and Cat has id , name , and age fields.

Then we defined the Pet union type, which is a union of Dog and Cat as follows:

const PetType = new graphql.GraphQLUnionType({
  name: 'Pet',
  types: [DogType, CatType],
  resolveType(value) {
    if (value instanceof Dog) {
      return DogType;
    }
    if (value instanceof Cat) {
      return CatType;
    }
  }
});

Note that we have an array of types and a resolveType method instead of the resolve method.

Then finally, we create our query type so that we can return a response to the user as follows:

const queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    pet: {
      type: PetType,
      args: {
        id: { type: graphql.GraphQLString }
      },
      resolve: (_, { id }) => {
        return pets[id];
      }
    }
  }
});

The resolve function gets the pets entry by id and returns it, and we specified that the type we return is the PetType .

Once we did that, we can make our query using inline fragments as follows:

{
  pet(id: "1"){
    __typename,
    ...on Dog {
      id
      name
    }
    ...on Cat {
      id
      name
      age
    }
  }
}

In the query above, we discriminated between the fields of Dog and Cat by using the ...on operator. __typename gets the type of the object returned.

With that query, we should get:

{
  "data": {
    "pet": {
      "__typename": "Dog",
      "id": "1",
      "name": "Jane"
    }
  }
}

since we have a Dog instance with key '1' in pets .

On the other hand, if we make a query for Pet with ID 2 as follows:

{
  pet(id: "2"){
    __typename,
    ...on Dog {
      id
      name
    }
    ...on Cat {
      id
      name
      age
    }
  }
}

We get:

{
  "data": {
    "pet": {
      "__typename": "Cat",
      "id": "1",
      "name": "Jane",
      "age": 11
    }
  }
}

since we have a Cat instance as the object with key '2' in pets .

Conclusion

We can create types with GraphQLObjectType constructor to create object types.

To create union types, we can use the GraphQLUnionType , then we have to resolve the type in the resolveType method by checking the type of the object and returning the right one.

We can query union types with inline fragments and check the type with __typename .