I can't keep track of what Enzyme supports or doesn't anymore. So I don't know if I expect this to even work at all with Enzyme TBH.
But here is my issue:
it.only("shows featured companies listed by name", async () => {
const newCompanies = [...companies];
mockPostGraphQL("/graphql", query, newCompanies);
const featuredCompanies = mount(<featuredCompanies />);
const companies = findAttribute(featuredCompanies, "company");
expect(companies.length).to.equal(5);
});
FeaturedCompanies.tsx
const FeaturedCompanies = () => {
const [featuredCompanies, setData] = useState([]);
useEffect(() => {
const fetchCompanies = async () => {
const companies: Array<Company> = await findFeaturedCompanies();
setData(companies);
};
fetchCompanies();
}, []);
return (
<>
<Companies companies={featuredCompanies} />
</>
);
};
it seems to be running my expect() before the async is finished fetching companies so the test fails too early as there are no companies yet. I'm resolving async calls with await on the prod code side, and I don't see any where in my test or otherwise that I'm missing an await.
This is the nature of JavaScript. fetchCompanies produces a Promise. Your test can't possibly reliably assert things after that promise has resolved, unless it has access to the promise and can await it.
The solution is to mock out findFeaturedCompanies, and await that in your test after the effect is started, and then you'll be able to call wrapper.update() and test the results.
What do you mean by after the effect is started? What starts the effect, the mount I assume is what you mean.
Lets take a look at an old shallow test of mine:
it('fetches countries', async () => {
mockGet('/api/v1/countries', {});
const homePage = shallow(<HomePageContainer store={store} />);
await homePage.childAt(0).props().fetchCountries();
const newState = store.getState();
expect(newState.country.countries).to.exist;
});
Yes, here I had access to props so I could await on that fetch. But with hooks you don't anymore.
The solution is to mock out findFeaturedCompanies, and await that in your test
I'm using nock, and findFeaturedCompanies is awaiting on the nock call:
async function findFeaturedCompanies() {
const response = await fetchFeaturedCompanies(),
companies = response.body.data.featuredCompanies;
return companies;
}
And since I'm awaiting it in my useEffect as well, I don't see why I need to await it again from my test here. Mount should run the lifecycle of the component _before_ it hits my expect.
Yes, hooks change the model drastically.
The issue is that your test runs long before fetchFeaturedCompanies is finished. You have to await it again because await doesn't "sleep" or block the thread, it's just sugar for a promise .then.
In other words, there is no "lifecycle" of your component that's observable by anything, including React. React just updates whenever you effect happens to complete, and doesn't run anything afterwards. Your test can't know when it happens to complete unless you have something to await.
Ok yea I get that it's not blocking, just trying to figure out how to put that extra await in there due to how Hooks works (ughhh) somewhere in my test...this is weird...we relied on the lifecycle with classes, and I guess under the hood shallow and mount must have relied on observing that?
wrapper.update() - why?
Because enzyme has no way of knowing when the react render tree has updated - wrapper.update tells enzyme to check again.
Yes, with classes, the lifecycle happens through observable invoked methods. With hooks, there is no lifecycle whatsoever - components just rerender when needed, and due to the nature of the JS language itself, it's impossible to observe closed-over things, and so there's no way to test these kinds of changes without coupling tests to implementation details.
Ok right, so if I can't get to fetchCompanies via props on a Hook now due to closure, how the heck is my test going to be able to await fetchCompanies? fetchCompanies is called internally via closure, internally via useEffect(). So not sure how I can await something I can't get to, you know what I mean?
Let me work on it, I have an idea on inverting dependencies. I'll hopefully come back with a working example. Thanks @ljharb !
Yes, exactly. You can use jest's module mocking to spy on it, but otherwise, you'd have to rearchitect.
Yea I'd prefer not to use a mocking framework like Jest (I don't like magic frameworks like that much, and it leads to fragile and to me unreadable code and just unnecessary learning curves).
But I'll have to rearchitect and hopefully share what I come up with! I plan on blogging when I do so I'll share that post back in here.
Sounds good, thanks. Will close in the meantime.