I was facing some console error messages with components relying that had FontAwesomeIcons in them. Didn't stop tests from passing, but made for some overly chatty tests. I'm not an expert on Jest at this point, so it took a bit of fumbling around to settle on something that worked. I'd like to add some documentation for other newbies, so I first want to know if the way I did it is any good.
First I used spyOn(console, "error"); to suppress the errors, but of course that hides other problems.
I'm using create-react-app, and created a manual mock insrc/__mocks__/@fortawesome/react-fontawesome.js, with this code:
import React from "react";
export function FontAwesomeIcon(props) {
return <i className="fa" />;
}
That works for me. I'd rewrite it as docs, of course, but is the approach good?
@chellman what were the errors?
They looked like this:
console.error node_modules/@fortawesome/react-fontawesome/index.js:274
Could not find icon { prefix: 'fas', iconName: 'chevron-right' }
I'm using the library method of including icons, with index.js importing everything.
@chellman that look like a valid error to me. Is that icon being used without being added to the library?
No, the icons are all there. This works fine in normal use in the browser - it's the unit testing context where it is falling down for me, hence the [apparent] need for the mock.
@chellman I had the same issue - which landed me here. Turns out, in my case, my offending test was testing a component that ultimately referenced font awesome icons, but was not including the file that actually loads the font awesome library.
I.e., in my app, the font awesome icons library is loaded with the Root component... but the test did not include the Root component while the component under test did have a child component referencing font awesome icons.
I hope this helps you.
@edubs23 You're right about the situation - I was unit testing a component on its own. I'm not an expert on testing methodology, but since React encourages components to be as relatively standalone as possible, I'm sure testing a component by itself is not an uncommon thing to do.
Just so it's clear, the solution I have of mocking FontAwesomeIcon seems to work great, I just wanted to confirm, from other people who have tested components that use it, that I'm not unwittingly doing something dumb. So far it seems like what I'm doing is sound, so I should probably just add a little bit to the documentation about how this can work.
@chellman if you are expecting the component to be tested and used on its own you might want to explore a different model than using library.add. You can see a hint in the Typescript documentation: https://github.com/FortAwesome/react-fontawesome.
I'm going to go ahead and close this since it's not really an issue with this project. I don't think mocking this out would be advisable. (It's showing you a legitimate error.)
@robmadole Understandable, I suppose. I don't think it's a problem with the project, just something other people might face if they, as I did, use the library approach. The documentation pretty clearly recommends that to avoid tedium, but it doesn't mention this as a possible side effect.
So now, having heard some feedback here, I'm advocating adding some docs that say something like "While using a library is convenient, you might run into issues with unit testing child components on their own. If you do, you can use explicit import, or mock FontAwesome icon". If I'm testing functionality that doesn't rely on the display of the icons, isn't mocking appropriate? If the answer is no and there's something else I need to wrap my head around, of course that's fine too.
Meantime, I've added a PR with what I think might work.
I have a small improvement to your excellent mock, so your tests/snapshots can confirm that the desired icon is rendered (not just any fa icon):
import React from "react";
export function FontAwesomeIcon(props) {
let className = `fa ${props.icon}`;
return <i className={className} />;
}
(for some reason I couldn't get interpolation to work inside the attribute value so I had to use an interim variable instead of <i className="fa {props.icon}" />)
Good point, @alexch! This should cover it with no interim variable 鈥斅爅ust put the expression in curly braces (not quotes/primes).
import React from "react";
export function FontAwesomeIcon(props) {
return <i className={`fa ${props.icon}`} />;
}
Quadruple interpolation FTW! :-)
Seems to work for me also
Thank you very much for this. It was really helpful. I ended modifying the mock a little bit for brand icons and size:
import React from 'react'
export function FontAwesomeIcon({ icon, size }) {
const iconClass = Array.isArray(icon) ? icon.join('-') : icon
return <i className={`fa ${iconClass} ${size}`} />
}
If you are using create-react-app there is an easier way than mocking. Create a module that just does the registering of the icons, maybe named registerFaIcons.js. This can then be used by both your React application as well as the Jest tests.
import { library } from '@fortawesome/fontawesome-svg-core';
import {
faSpinner
} from '@fortawesome/free-solid-svg-icons';
export default function registerIcons() {
library.add(
faSpinner
);
}
So with create-react-app, within your setupTests.js file you can put code that imports and calls the registerIcons method. This will register the icons for use by all tests and the FA icons will render as expected.
import registerFaIcons from './registerFaIcons';
registerFaIcons();
I thought I'd share our version of the mock. We're outputting an empty <svg> element to match the current implementation, with only the appropriate class names (which will help any element getters in the test). We also allow the icon prop to be an object as imported from the Font Awesome libraries.
/**
* Mocks @fortawesome/react-fontawesome.js to return a simple, empty <svg> element
* with the correct class name for the given `icon` prop. We use this to keep full
* <svg> content out of our rendered component tests.
*/
import React from 'react'
import PropTypes from 'prop-types'
export function FontAwesomeIcon (props) {
const classNames = ['svg-inline--fa']
if (typeof props.icon === 'string') {
classNames.push(props.icon)
} else {
classNames.push(`fa-${props.icon.iconName}`)
}
return <svg className={classNames.join(' ')} />
}
FontAwesomeIcon.propTypes = {
icon: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
prefix: PropTypes.string,
iconName: PropTypes.string,
icon: PropTypes.arrayOf(PropTypes.any)
})
]).isRequired
}
@louh :
Can you provide full setup, like how to setup mock config or in which folder to store mock for auto configuration?
I see also that new version is @fortawesome/react-fontawesome/index.js
@raDiesle We don't do anything too differently from a typical Jest setup, and you can investigate how we've set it up on our repository: https://github.com/streetmix/streetmix/blob/master/test/__mocks__/%40fortawesome/react-fontawesome.js
I thought I'd share our version of the mock. We're outputting an empty
<svg>element to match the current implementation, with only the appropriate class names (which will help any element getters in the test). We also allow theiconprop to be an object as imported from the Font Awesome libraries./** * Mocks @fortawesome/react-fontawesome.js to return a simple, empty <svg> element * with the correct class name for the given `icon` prop. We use this to keep full * <svg> content out of our rendered component tests. */ import React from 'react' import PropTypes from 'prop-types' export function FontAwesomeIcon (props) { const classNames = ['svg-inline--fa'] if (typeof props.icon === 'string') { classNames.push(props.icon) } else { classNames.push(`fa-${props.icon.iconName}`) } return <svg className={classNames.join(' ')} /> } FontAwesomeIcon.propTypes = { icon: PropTypes.oneOfType([ PropTypes.string, PropTypes.shape({ prefix: PropTypes.string, iconName: PropTypes.string, icon: PropTypes.arrayOf(PropTypes.any) }) ]).isRequired }
In Jest test this simple mock worked for me
jest.mock('@fortawesome/react-fontawesome', () => ({
FontAwesomeIcon: () => {
return <svg />;
},
}));
Most helpful comment
If you are using create-react-app there is an easier way than mocking. Create a module that just does the registering of the icons, maybe named registerFaIcons.js. This can then be used by both your React application as well as the Jest tests.
So with create-react-app, within your setupTests.js file you can put code that imports and calls the registerIcons method. This will register the icons for use by all tests and the FA icons will render as expected.