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.
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" }
]
}
}
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
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.
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];
}
}
@gtsop thanks for the issue! A couple thoughts:
First:
The approach of wrapping a component with the data grabber and exporting the component that consumes it for testing is not unique to gatsby.
That approach is basically the suggested approach for any higher order component based library (like redux).
Second
The actual code in use is not tested (most important)
This is not true. When you test the PureComponent export you are testing your application code. The StaticQuery is ours, and we have tests. For example, you don't need to test that React takes your JavaScript and actually updates the DOM, they have tests for that.
Onto your idea!
I think there is some merit here. It definitely would be an ergonomic improvement for it to just work. Just spit-balling here to sort out the idea more for a generic use.
I think this could work with a folder like /__mock_queries__/
Then we could either have a naming scheme, like match the component name, or the query name (though not all gql queries have names).
ultimately we would want to verify that the mock json payload matches the query. If they don't we would need to throw an error and tell them to update.
Probably more that needs to be fleshed out here. But I think this has merit to explore.
Questions I have:
I think there is some merit here. It definitely would be an ergonomic improvement for it to just work.
Glad to hear that!
Then we could either have a naming scheme, like match the component name, or the query name (though not all gql queries have names).
The basic scenario in which the file name matches the query name is easily implemented. In the example, the query name is ContinueButtonText and the selected file is __mocks__/queries/continue-button-text.json. See __mocks__/gatsby.js in the example.
ultimately we would want to verify that the mock json payload matches the query. If they don't we would need to throw an error and tell them to update.
Me spitballing as well, if we go as far as validation, that would allow for automatic generation of the mock with a bit additional effort.
Questions I have:
* Can this support other use-cases or cause difficulty for other testing needs?
Can't think of any, sorry. Started using gatsby a month ago.
* How do we make it work with implicit query data
Like?
Hiya!
This issue has gone quiet. Spooky quiet. 馃懟
We get a lot of issues, so we currently close issues after 30 days of inactivity. It鈥檚 been at least 20 days since the last update here.
If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not stale" to keep this issue open!
As a friendly reminder: the best way to see this issue, or any other, fixed is to open a Pull Request. Check out gatsby.dev/contribute for more information about opening PRs, triaging issues, and contributing!
Thanks for being a part of the Gatsby community! 馃挭馃挏
Hey again!
It鈥檚 been 30 days since anything happened on this issue, so our friendly neighborhood robot (that鈥檚 me!) is going to close it.
Please keep in mind that I鈥檓 only a robot, so if I鈥檝e closed this issue in error, I鈥檓 HUMAN_EMOTION_SORRY. Please feel free to reopen this issue or create a new one if you need anything else.
As a friendly reminder: the best way to see this issue, or any other, fixed is to open a Pull Request. Check out gatsby.dev/contribute for more information about opening PRs, triaging issues, and contributing!
Thanks again for being part of the Gatsby community! 馃挭馃挏
@gtsop sorry for the delay!
The basic scenario in which the file name matches the query name is easily implemented. In the example, the query name is ContinueButtonText and the selected file is __mocks__/queries/continue-button-text.json. See __mocks__/gatsby.js in the example.
What if the query is unnamed? We could choose some heuristics but that "magic" could get weird and non-discoverable. Or we could only match if there is a name for the query.
ultimately we would want to verify that the mock json payload matches the query. If they don't we would need to throw an error and tell them to update.
Me spitballing as well, if we go as far as validation, that would allow for automatic generation of the mock with a bit additional effort.
I don't think for tests you want auto generated data, you typically want idempotent data so your test is isolated to the code, not the fixture.
The more I think about this I feel like the existing suggestion from our docs may be best. Rarely do tests need fixture re-use, so isolating to your test file has the benefit of ease-of-consumption. Additionally, migrating to a mock file comes with some discoverability concerns that we would need to work out.
You mentioned The actual code in use is not tested (most important) as a motivation. Can you explain this more? I'm not sure how I see the actual code is not being tested with our approach.
@blainekasten
You mentioned
The actual code in use is not tested (most important)as a motivation. Can you explain this more? I'm not sure how I see the actual code is not being tested with our approach.
Fromt he docs:
Source
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
export const PureHeader = ({ data }) => (
<header>
<h1>{data.site.siteMetadata.title}</h1>
</header>
)
export const Header = props => {
const data = useStaticQuery(graphql`
query {
site {
siteMetadata {
title
}
}
}
`)
return <PureHeader {...props} data={data}></PureHeader>
}
export default Header
Test
import React from "react"
import renderer from "react-test-renderer"
import { PureHeader as Header } from "../header"
describe("Header", () => {
it("renders correctly", () => {
// Created using the query from Header.js
const data = {
site: {
siteMetadata: {
title: "Gatsby Starter Blog",
},
},
}
const tree = renderer.create(<Header data={data} />).toJSON()
expect(tree).toMatchSnapshot()
})
})
The unit test tests the PureHeader component, however, the application will make use of the Header component. Thus, the code being used (Header component) is not unit tested. Since the Header component does three things (requests data, passes props to PureHeader, passes data to PureHeader) it isn't a good idea to assume it just works in the unit test suite
@gtsop I think you're conflating a few things. In the example above, your test is actually testing your application code, which is what you want to test. The fact that you aren't testing the data fetching, or prop passing feels moot and potentially wrong if you were to test those.
For example, testing the fetching would be a waste of time. That code is ours, and we have tests to ensure it works. If you wrote a test to show that it returns data it would literally never break because we would never ship a breaking change without a major version and an upgrade path.
As far as the props and data passing. Those are super small integration points that if you failed to do that, your app would break pretty obviously (e.g., the component would crash).
I feel reassured that the way we advertise testing these components is accurate. Its very akin to testing redux connected components. I'm going to close this issue now as I don't see us doing anything actionable with it. Let's keep this conversation going here though and I'd love to be convinced otherwise.
@blainekasten
For example, testing the fetching would be a waste of time. That code is ours, and we have tests to ensure it works. If you wrote a test to show that it returns data it would literally never break because we would never ship a breaking change without a major version and an upgrade path.
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.
As far as the props and data passing. Those are super small integration points that if you failed to do that, your app would break pretty obviously (e.g., the component would crash).
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.
export const PureHeader = ({ data, specialUnusualUserProp }) => (
<header>
<h1>{data.site.siteMetadata.title}</h1>
{specialUnusualUserProp && (<p>Oh you're so special!</p>)}
</header>
)
export const Header = props => {
const data = useStaticQuery(graphql`
...
`)
return <PureHeader {...props} data={data}></PureHeader>
}
Assume I test PureHeader and add a special unit test for asserting the appearance of "Oh you're so special" text when specialUserProp=true.
Now my intern web-developer goes and modifies the Header component because {...props} looks redundant:
// no props passing :(
return <PureHeader data={data}></PureHeader>
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:
@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.
This looks useful for those who don't want to create multiple components for testing purposes:
https://www.npmjs.com/package/react-hooks-compose
In a nutshell:
import composeHooks from 'react-hooks-compose';
const useForm = () => {
const [name, setName] = useState('');
const onChange = e => setName(e.target.value);
return { name, onChange };
};
// Other props (in this case `icon`) can be passed in separately
const FormPresenter = ({ name, onChange, icon }) => (
<div className="App">
<div>{icon}</div>
<h1>Hello, {name}!</h1>
<input value={name} onChange={onChange} />
</div>
);
export default composeHooks({ useForm })(FormPresenter);
@gtsop [...] try this and let me know how it works for your use case Mock Gatsby's useStaticQuery with Jest.
Wow this looks like a great solution to the problem. Will try it out next for certain.
I'm also interested to know how things might have changed for your set-up since this issue was closed.
They haven't changed really, I used the setup I described above and the project was over. However, I have a constant mental friction in my head when a new project comes up and people suggest we use Gatsby because I think of the pain I'll go through trying to setup the unit tests. Maybe your solution will put an end to it.
@balibebas An update on this:
I'm also interested to know how things might have changed for your set-up since this issue was closed.
While structuring our latest project I reaaally didn't want to deal with this problem for my component tests. I explored a different approach that pushed away all the static queries under the umbrella of a react hook named useCMS. (used the fixtures as described in my original post for testing purposes)
// src/hooks/useCMS.js
export default function useCMS() {
// Run static queries and return a single object with all the data
return {
homePageData: ...,
someOtherPageData: ...,
}
}
Then, all of my components under src/pages have knowledge of this useCMS hook and use it to propagate the data downwards.
// src/pages/index.js
import React from "react"
import HomePage from "@/components/HomePage"
import useCMS from "@/hooks/useCMS"
export default function Index() {
const { homePageData } = useCMS()
return <HomePage {...homePageData} />
}
The result is I've achieved perfect isolation between my UI components and the source of the data. All my components in src/components receive the appropriate data in their props and have zero knowledge of static queries or useCMS.
//src/components/HomePage/index.js
import React from "react"
export default function HomePage({ all, theData, iNeed }) {
return <> .... </>
}
Although I've essentially dropped the convenience of having my data source right next to my component, this approach made my dev experience way smoother and helped me keep the majority of the code (and tests) clean while dumping all the ugliness in that useCMS hook. Now, passing data to a component during unit tests is no different than doing it in pure react.
//src/components/HomePage/test.js
import React from "react"
import HomePage from "./"
describe('Home Page', function () {
it('renders', function () {
const mockData = { all: '', theData: '', iNeed: '' }
someRenderFunction(<HomePage {...mockData} />)
expect(...)
})
})