I'm running into an issue while trying to do some basic smoke testing for React components that use react-highcharts. My typical method with basic Jest yields an error:
it('renders without crashing', () => {
const div = document.createElement('div');
render(<MyComponent {...props} />, div);
});
—>
InvalidCharacterError
at exports.name (node_modules/jest-environmentjsdom/node_modules/jsdom/lib/jsdom/living/helpers/validate-names.js:10:11)
at a.createElement (node_modules/highcharts/highcharts.js:17:221)
at Object.a.svg.z.init (node_modules/highcharts/highcharts.js:92:155)
at Object.z.createElement (node_modules/highcharts/highcharts.js:63:3)
at Object.a.svg.z.createElement (node_modules/highcharts/highcharts.js:107:525)
at Object.a.svg.z.init (node_modules/highcharts/highcharts.js:101:44)
at Object.a.svg.a.VMLRenderer.B (node_modules/highcharts/highcharts.js:109:320)
at Object.N.getContainer (node_modules/highcharts/highcharts.js:252:329)
From some interwebs sleuthing, it seems that this is an inherent problem with rendering <ReactHighcharts /> as a child component. How can I get around this without restructuring my component or complicating my testing?
Since the problem is rendering <ReactHighcharts /> as a child component, and we're just trying to make sure the parent component doesn't blow up, we can use Enzyme's shallow method to render only that parent component without the children:
it('renders without crashing', () => {
expect(shallow(<MyComponent {...props} />).exists()).toBeTruthy();
});
Related
I use enzyme with sinon for unit testing React components. Normally, when it comes to testing instance methods I just spy on the method on instance of the component and assert respectively.
However, I have this global function that I use in many components of the app, which is a named export. sinon throws if I try to spy on that.
import { openModel } from '../global/handlers/';
<Block
onRemove={(data) => openModal(...args)}
/>
So, currently I am calling prop method onRemove to assert that openModal gets called with the arguments but I can't really spy on the exported method i.e. openModal.
I understand that I need to provide a context to this function to be able to spy on the underlying function but I am not really sure what's the preferred way of doing something like this.
PS: I would be happy to provide more details if need be.
If you are using webpack to build your test code, then you can use inject-loader to replace the imported module with a stub:
describe('Your component', () => {
let openModalSpy;
let Component;
// Use whatever the path to your component is
const injectImports = require('inject-loader!components/Component');
beforeEach(() => {
openModalSpy = sinon.spy();
Component = injectImports({
openModal: openModalSpy
}).default;
})
it('calls open modal with data argument', () => {
const wrapper = shallow(
<Component />
);
// Do something that will result in openModal being called
expect(openModalSpy).to.have.been.calledWith({
// some data
});
}
}
I have a button component that creates a react-router Link element. It also allows an onClick function to be passed in for additional functionality (e.g. sending a Google Analytics event).
I have included this component in a parent, like so:
export default class Page extends Component {
const doSomething = () => {
//do a thing to test here
}
return (
<div>
<Button
onClickFn{() => doSomething()}
linkToUrl='/other/page' //this creates a <Link> inside the button
/>
</div>
)
}
Problem comes when I want to test that doSomething is being triggered correctly. I have used Enzyme mount to create the test Page component including the button. When I simulate a click I get the following error
'<Link>s rendered outside of a router context cannot navigate.'
because the Link in the button has no context. Is there a way of mocking this or preventing the error from showing? Or is there a better way of testing this functionality?
In your test, you will need to render the component within a <Router>. You can take a look at the tests for the <Link> component for examples on how to do this.
The basic idea is to create a memory history instance, pass that to the <Router>, and then render the <Link> inside of a <Route> passed to that. It sounds a bit involved, but it is fairly simple.
import { createMemoryHistory } from 'history'
it('clicks', () => {
const history = createMemoryHistory()
const App = () => (
<Router history={history}>
<Route path='/' component={Page} />
</Router>
)
})
Building on top of Paul's answer, here's a more detailed example for testing the onClick of a Button (or its Link child to be more precise). The example uses the testing libraries mocha (BDD test runner), chai (BDD assertions), enzyme (React testing utility), and sinon (test doubles).
import React from 'react';
import { Router, Route } from 'react-router';
import { createMemoryHistory } from 'history';
import MyCustomPage from '/.index';
describe('MyCustomPage', function(){
it('stores data when clicking the link', function() {
// Arrange
const Page = () => (
<MyCustomPage foo="foo" bar="bar" />
);
const container = enzyme.mount(
<Router history={createMemoryHistory()}>
<Route path="/" component={Page} />
</Router>
);
// Act
container.find('#my-link').simulate('click');
// Assert
expect(sessionStorage.setItem).to.have.been.calledWith('key', 'value');
});
});
This doesn't work for me. Please make the Plunkr below work.
describe("trying a test", () => {
beforeEach(() => {
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
TestBed.configureTestingModule({
declarations: [myCmp, ChildCmp]
});
});
it("test should work", () => {
const fixture = TestBed.createComponent(myCmp);
const div = fixture.debugElement.children[0];
const childCmp = div.queryAll(By.directive(ChildCmp));
const divEl = div.queryAll(By.css('div'));
divEl[0].triggerEventHandler('click', <Event>{});
fixture.detectChanges();
expect(childCmp[0].nativeElement.textContent).toBe("updated value");
});
});
https://plnkr.co/edit/wWJMDi3ZFC6RTSvCw4HH?p=preview
This is no longer an issue for me. For the most part (with one exception I will outline below), both my parent components and child components get updated in my real app code, though I haven't updated the Plunkr.
I just use .and.callThrough() on the spyed on parent component method that is called, when something is clicked on the template. Rather than a simple spy.
I do have an issue with ngFor rendered child components not updating. This is my other question: Angular 2 unit testing: Make ngFor rendered, child components' templates change?
When I test a react component, what are the best practices and what things should I test for? In normal tests I usually just test if the correct state+input leads to the correct state+output
But React components are a bit different. They have state+props+userInput which result in state+markup.
This can lead to many, many different potential scenarios. Do I need to test for the resulting state of all those scenarios?
The markup can be huge. Should I test if the whole markup-tree is as expected? Or just part of it? How do I determine what part of the markup to test?
First obvious things to keep in mind:
If the logic of the your components can be encapsulated into modules and tested independently, then do it. Example: For a calculator component, the calculations themselves can be tested independently of the component. I know this is obvious, but just to make the point.
Break your components apart into smaller ones and test each of them granularly.
Regarding the component, always test:
If the correct props will render the correct output (HTML).
If the correct user interaction (click, key presses...) will fire the appropriate events and lead to the correct output (HTML). I usually don't deal with the component state at all during unit tests, as I don't find this a good practice. To test a TV you shouldn't have to open it.
If you are not sure about what library to use to test React components, I'd strongly recommend Enzyme.
Examples from their GitHub page:
describe('<MyComponent />', () => {
it('renders three <Foo /> components', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.find(Foo)).to.have.length(3);
});
it('renders an `.icon-star`', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.find('.icon-star')).to.have.length(1);
});
it('renders children when passed in', () => {
const wrapper = shallow(
<MyComponent>
<div className="unique" />
</MyComponent>
);
expect(wrapper.contains(<div className="unique" />)).to.equal(true);
});
it('simulates click events', () => {
const onButtonClick = sinon.spy();
const wrapper = shallow(
<Foo onButtonClick={onButtonClick} />
);
wrapper.find('button').simulate('click');
expect(onButtonClick).to.have.property('callCount', 1);
});
});
I am testing an angularjs directive that manipulates the DOM.
I am trying to get the element in my Jasmine spec, so that I can test the functionality of the directive. However, when I use document.getElementsByClassName or TagName or ID, it doesn't return anything. Does anyone have ideas about this?
html = document.getElementsByClassName('analog');
console.dir(html);
If you create a test in headless browser/chrome etc., you could append a dummy object, for example JQuery node, then remove that node in afterEach.
E.g.
beforeEach(() => {
var mockHtml = $('<div class="form-group" style="position: absolute;left: -10000px;"><input class="testInput" id="some_input"></div>');
$('body').append(mockHtml);
});
afterEach(() => {
$('.form-group').remove();
});