Results:
Loading...

React Data GridTesting AG Grid

Testing with Jest

If you're using Modules then you will have to make the following configuration changes to accommodate ES Modules - if you're using Packages then this configuration is not required.

In order to test AG Grid with Jest you'll need to make the following configuration changes:

In jest.config.js add the following lines:

module.exports = {
  "transform": {
    "^.+\\.(ts|tsx|js|jsx|mjs)$": [
      "babel-jest"  // or "ts-test" or whichever transformer you're using
    ]
  },
  transformIgnorePatterns: ['/node_modules/(?!(@ag-grid-community|@ag-grid-enterprise)/)']
}

Waiting for the Grid to be Initialised

Due to the asynchronous nature of React we cannot simply mount the Grid and assume it'll be ready for testing in the next step - we need to wait for the Grid to be ready before testing it.

We can do this in one of two ways - wait for the gridReady event to be fired, or wait for the Grid API to be set.

The first requires a code change and can be tricky to hook into - the latter is unobtrusive and easier to use.

We can create a utility function that will wait for the Grid API to be set for a set amount of time/attempts:

export const ensureGridApiHasBeenSet = component => {
     return waitForAsyncCondition(() => {
         return component.instance().api !== undefined
     }, 5)
 };

 export const waitForAsyncCondition = (condition, maxAttempts, attempts = 0) => new Promise(function (resolve, reject) {
     (function waitForCondition() {
         // we need to wait for the gridReady event before we can start interacting with the grid
         // in this case we're looking at the api property in our App component, but it could be
         // anything (ie a boolean flag)
         if (condition()) {
             // once our condition has been met we can start the tests
             return resolve();
         }

         attempts++;

         if (attempts >= maxAttempts) {
             reject("Max timeout waiting for condition")
         }

         // not set - wait a bit longer
         setTimeout(waitForCondition, 10);
     })();
 });

The first function is what we'll use to wait for the Grid API - the 2nd one is more generic and will be useful for waiting for Grid components to be ready (see later).

We can use ensureGridApiHasBeenSet before our tests are executed, typically in the beforeEach hook:

beforeEach((done) => {
     component = mount((<GridWithStatefullComponent/>));
     agGridReact = component.find(AgGridReact).instance();
     // don't start our tests until the grid is ready
     ensureGridApiHasBeenSet(component).then(() => done(), () => fail("Grid API not set within expected time limits"));

 });

 it('stateful component returns a valid component instance', () => {
     expect(agGridReact.api).toBeTruthy();

     // ..use the Grid API...
 });

We can now safely test the Grid component safe in the knowledge that it's been fully initialised.

Waiting for Grid Components to be Instantiated

In the same way we need to wait for the Grid to be ready we also need to do something similar for user supplied Grid components.

For example, let us suppose a user provides a custom Editor Component and wants to test this within the context of the Grid.

class EditorComponent extends Component {
     constructor(props) {
         super(props);

         this.state = {
             value: this.props.value
         }
     }

     render() {
         return (
             <input type="text"
                    value={this.state.value}
                    onChange={this.handleChange}
                    style={{width: "100%"}} />
         )
     }

     handleChange = (event) => {
         this.setState({value: event.target.value});
     }

     getValue() {
         return this.state.value;
     }

     // for testing
     setValue(newValue) {
         this.setState({
             value: newValue
         })
     }

     isCancelBeforeStart() {
         return false;
     }

     isCancelAfterEnd() {
         return false;
     };
 }

 class GridWithStatefullComponent extends Component {
     constructor(props) {
         super(props);

         this.state = {
             columnDefs: [{
                 field: "age",
                 editable: true,
                 cellEditor: EditorComponent
             }],
             rowData: [{ age: 24 }]
         };
     }

     onGridReady(params) {
         this.api = params.api;
     }

     render() {
         return (
             <div className="ag-theme-balham">
                 <AgGridReact
                     columnDefs={this.state.columnDefs}
                     onGridReady={this.onGridReady.bind(this)}
                     rowData={this.state.rowData} />
             </div>
         );
     }
 }

We can now test this Editor Component by using the Grid API to initiate testing, gain access to the created Editor Instance and then invoke methods on it:

it('cell should be editable and editor component usable', async() => {
     expect(component.render().find('.ag-cell-value').html()).toEqual(`,[object Object],`);

     // we use the API to start and stop editing
     // in a real e2e test we could actually double click on the cell etc
     agGridReact.api.startEditingCell({
         rowIndex: 0,
         colKey: 'age'
     });

     await waitForAsyncCondition(
         () => agGridReact.api.getCellEditorInstances() &&
               agGridReact.api.getCellEditorInstances().length > 0, 5)
               .then(() => null, () => fail("Editor instance not created within expected time"));

     const instances = agGridReact.api.getCellEditorInstances();
     expect(instances.length).toEqual(1);

     const editorComponent = instances[0];
     editorComponent.setValue(50);

     agGridReact.api.stopEditing();

     await waitForAsyncCondition(
         () => agGridReact.api.getCellRendererInstances() &&
               agGridReact.api.getCellRendererInstances().length > 0, 5)
               .then(() => null, () => fail("Renderer instance not created within expected time"));

     expect(component.render().find('.ag-cell-value').html()).toEqual(`,[object Object],`);
 });

Note that we make use of the waitForAsyncCondition utility described above to wait for the Editor Component to be instantiated.

We also use the Grid API to initiate and end testing as we're can't readily perform double clicks in a unit testing environment (but could if doing e2e with something like Protractor for example).

Testing React Hooks with Enzyme

By default testing libraries won't return an accessible instance of a hook - in order to get access to methods you'll need to wrap your component with a forwardRef and then expose methods you want to test with the useImperativeHandle hook.

import React, {forwardRef, useImperativeHandle, useState} from 'react';
import {AgGridReact} from 'ag-grid-react';

export default forwardRef(function (props, ref) {
    const columnDefs = [...columns...];
    const rowData = [...rowData...];

    const [api, setApi] = useState(null);

    const onGridReady = (params) => {
        setApi(params.api);
    };

    useImperativeHandle(ref, () => {
        return {
            getApi() {
                return api;
            }
        }
    });

    return (
        <AgGridReact
            columnDefs={columnDefs}
            onGridReady={onGridReady}
            rowData={rowData}
        />
    );
});

You can then test this hook by accessing it via a ref:

const ensureGridApiHasBeenSet = async (componentRef) => {
    await act(async () => {
        await new Promise(function (resolve, reject) {
            (function waitForGridReady() {
               // access the exposed "getApi" method on our hook
                if (componentRef.current.getApi()) {
                    if (componentRef.current.getApi().getRowNode(8)) {
                        return resolve();
                    }

                }
                setTimeout(waitForGridReady, 10);
            })();
        })

    });
};

beforeEach(async () => {
    const ref = React.createRef()
    component = mount(<App ref={ref}/>);
    agGridReact = component.find(AgGridReact).instance();
    await ensureGridApiHasBeenSet(ref);
});

Note that we're accessing exposed getApi method via the ref: componentRef.current.getApi().

A full working example can be found in the following ,[object Object],.