Suggestion: Graphql useStaticQuery mocking for tests
See original GitHub issueSummary
This is a suggestion for mocking graphql queries for unit testing. I’ve used the following pattern in a project to overcome some of the problems that come with the documented way of solving this issue. It can be documented on the website as a solution or integrated into the project.
Basic example
src/components/continue-button.component.js
import React from "react";
export default function ContinueButton () {
const text = getContinueText();
return <button>{text}</button>
}
function getContinueText() {
const data = useStaticQuery(graphql`
query ContinueButtonText {
allButtonJson {
nodes {
continue
}
}
}
`);
return data.allButtonJson.nodes[0].continue;
}
`
src/components/continue-button.component.spec.js
import React from "react";
import { mount } from "enzyme";
import ContinueButton from "./continue-button.component";
describe('ContinueButton Component', function () {
it(`renders a button saying "Some text"`, function () {
const component = mount(<ContinueButton />);
expect(component.text()).toEqual("Some text");
});
})
__mocks__/queries/continue-button-text.json
{
"allButtonJson": {
"nodes": [
{ "continue": "Some text" }
]
}
}
Motivation
I’m working on a project and I need to inject some data from the data layer into some components. Initially I followed the documented way to test it (https://www.gatsbyjs.org/docs/testing-components-with-graphql/#testing-staticquery). I used the Pure<ComponenteName> pattern, however there are a few aspects I heavily disliked about it:
- The actual code in use is not tested (most important)
- There is a naming and code tax for every component to be tested. Especially for a simple, small component like the button in the example, I had to write esentially the same code twice (like the documentation example with the Header and PureHeader)
- I ended using a useStaticQuery call and an if/else clause for returning data during testing:
const data = useStaticQuery(graphql`
query ContinueButtonText {
allButtonJson {
nodes {
continue
}
}
}
`);
if (data) {
return data.allButtonJson.nodes[0].continue;
} else {
return "Some Text";
}
Which got very ugly as the returned data became more and more complex.
Implementation
I used the following code to wire up the query responses. I realize the use case is currently limited but I’m sure it can be extended to be more inclusive.
__mocks__/gatsby.js
const React = require("react")
const gatsby = jest.requireActual("gatsby")
const decamelize = require("decamelize");
module.exports = {
// ... {whatever the docs say}
graphql: function ([queryString]) {
return getQueryName(queryString);
},
useStaticQuery: function (queryName) {
const filePath = `./queries/${decamelize(queryName, '-')}.json`;
try {
return require(filePath);
} catch(err) {
console.warn(`Failed to require mock file for ${queryName}`);
return {};
}
},
}
function getQueryName (queryString) {
const captureGroup = queryString.match(/query\s([^\s]+).*{/);
if (captureGroup && captureGroup.length) {
return captureGroup[1];
}
}
Issue Analytics
- State:
- Created 4 years ago
- Comments:13 (4 by maintainers)
Top GitHub Comments
@gtsop Thanks for the mocking pattern. I haven’t tried it but it gives me ideas on how to structure fixtures for individual tests. Regarding the push-back from Blaine, try this and let me know how it works for your use case Mock Gatsby’s useStaticQuery with Jest. I’m also interested to know how things might have changed for your set-up since this issue was closed.
A word on unit testing: If it helps you isolate components that’s a good thing. But if it influences your code too much and you end up writing boilerplate just to be able to unit test that’s an antipattern and so I agree with some of the sentiments in the OP. In particular, your motivational arguments which seem to me to have been misunderstood or taken out of context during this thread. End of the day if Unit Testing isn’t’ easy people aren’t going to do it.
@blainekasten
The unit tests would never test gatsby’s data fetching code simply because that code is mocked during setting up jest, this is out of the question. It’s not about testing the library’s code. It’s about making sure the application code does what it is supposed to do.
This is an assumption that (apart from being arbitrary and wrong) a library shouldn’t make a decision on.
Why is this assumption wrong? Take this example:
Take this example in which a specific property allows for showing some extra text, a non-breaking feature.
Assume I test
PureHeader
and add a special unit test for asserting the appearance of “Oh you’re so special” text whenspecialUserProp=true
.Now my intern web-developer goes and modifies the
Header
component because{...props}
looks redundant:My unit tests pass, my integration tests pass (because I was to bored to re-assert the text thinking I’ve got a unit test for that case) and noone get’s any feedback the application has a broken feature.
The breakage happend because we didn’t unit test application code. It doesn’t matter how trivial it is and it won’t always lead to visible breakage.
But let’s not change the discussion of this thread to “Do I really need to unit test ‘X’ code?” or “What I should have done to avoid this breakage”. Gatsby (and any framework for that matter) should allow for 100% test coverage over the application code, it’s the developer’s decision as to wether they need to test something or not. This decision can’t and shouldn’t be made by the framework.
My solution may be incomplete, limited or on the wrong direction. Thus I accept that closing the issue is ok. However, the underlying problem persists.
And the problem is this: The gatsby-way for unit testing propses writing application code that can’t be unit tested and has 3 different responsibilities: