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);
});
});
Related
I have the next code i made a test but now i have a dilema strictly speaking with tdd for add
"this.loadCounter('anotherReq', 'anotherError', differentCallback);"
i have to reply the tests only for test the behavior but i not sure if this is necessary.
class Statistic extends PureComponent<Props, State> {
state = {
};
componentDidMount() {
this.loadCounter('suggestedReqCount', 'hasSuggestedReqsErrors', getCountSuggestedReqs);
this.loadCounter('anotherReq', 'anotherError', differentCallback);
}
loadCounter = async (
stateCounterKey: string, stateCounterErrorKey: string, loaderFunction: Function) => {
try {
const count = await loaderFunction();
this.setState({
[stateCounterKey]: count,
});
} catch (ex) {
this.setState({
[stateCounterErrorKey]: true,
});
}
}
}
this are the test that i have
test('Should set state with suggested requirement count', async () => {
const wrapper = mount(
<Statistic
intl={{
formatMessage: jest.fn(),
}}
/>,
);
wrapper.update();
await getCountSuggestedReqs();
expect(wrapper.state().suggestedReqCount).toBe(5);
});
test('Should on fail load suggested reqs update state', async () => {
getCountSuggestedReqs.mockReturnValueOnce(Promise.reject('Error creado'));
const wrapper = mount(
<Statistic
intl={{
formatMessage: jest.fn(),
}}
/>,
);
wrapper.update();
await getCountSuggestedReqs();
expect(wrapper.state().hasSuggestedReqsErrors).toBe(true);
});
The first rule of TDD is that we cannot write any production code before having a failing test.
This alone implies that if you want to add production code, you have to write a test that will require it.
That said, there are cases where I won't write a unit test and let the larger tests grab that functionality, like in cases of simple delegation - the code isn't doing any logic, just passing the same arguments to another function, so there's nothing really to test, and the wiring will be tested from the outside.
In this case, however, it seems to me that you're invoking that function twice in order to get different information.
You could expand the existing test to include the new data, but I'm not a fan of that option. I would rather leave existing tests and add new tests for new functionality. Notice that "As the tests become more specific, the code becomes more generic", is true here too. Perhaps when the third call to loadCounter comes in, you'll need to refactor the code to be more generic, but at this point I would just add the second unit test.
All that said, you should definitely separate your logic code and your component code. There's no reason this unit test would be an Enzyme test. The code in componentDidMount could be easily refactored to a function that could be unit tested by just a plain ol' test.
When writing unit tests for a React Native project I want to be able to test different snapshots based on different platforms.
I first tried jest.mock to mock Platform but seems to be async. This approach does work when I have two separate files, but I'd prefer to keep everything in one file if possible.
I tried jest.doMock because of this snippet from the documentation:
When using babel-jest, calls to mock will automatically be hoisted to the top of the code block. Use this method if you want to explicitly avoid this behavior.
https://facebook.github.io/jest/docs/en/jest-object.html#jestdomockmodulename-factory-options
However I'm still seeing undesirable results. When I console.log in the android test I see that Platform.OS is whatever I set the first doMock to be.
I also tried wrapping the mock in a beforeEach in a describe becasue I thought that might help with scoping
http://facebook.github.io/jest/docs/en/setup-teardown.html#scoping
describe('ios test', () => {
it('renders ui correctly', () => {
jest.doMock('Platform', () => {
const Platform = require.requireActual('Platform');
Platform.OS = 'ios';
return Platform;
});
const wrapper = shallow(<SomeComponent />);
const tree = renderer.create(wrapper).toJSON();
expect(tree).toMatchSnapshot();
});
});
describe('android test', () => {
it('renders ui correctly', () => {
jest.doMock('Platform', () => {
const Platform = require.requireActual('Platform');
Platform.OS = 'android';
return Platform;
});
const wrapper = shallow(<SomeComponent />);
const tree = renderer.create(wrapper).toJSON();
expect(tree).toMatchSnapshot();
});
});
Any ideas on how I can change the mock Platform for tests in the same file?
There are a lot of suggestions on how to solve this problem in another question, but none of them worked for me either, given the same requirements you have (tests for different OSs in the same suite file and in one test run).
I eventually worked around it with a somewhat clunky trivial helper function that can be mocked as expected in tests – something like:
export function getOS() {
return Platform.OS;
}
Use it instead of Platform.OS in your code, and then simply mock it in your tests, e.g.
it('does something on Android', () => {
helpers.getOS = jest.fn().mockImplementationOnce(() => 'android');
// ...
}
That did the trick; credit for the idea is due to this guy.
I am writing unit tests for my vuejs components in a larger application. My application state is all in the vuex store, so almost all of my components pull data from vuex.
I can't find a working example for writing unit test for this. I have found
Where Evan You says:
When unit testing a component in isolation, you can inject a mocked store directly into the component using the store option.
But I can't find a good example of how to test these components. I have tried a bunch of ways. Below is the only way I have seen people do it. stackoverflow question and answer
Basically it looks like the
test.vue:
<template>
<div>test<div>
</template>
<script>
<script>
export default {
name: 'test',
computed: {
name(){
return this.$store.state.test;
}
}
}
</script>
test.spec.js
import ...
const store = new Vuex.Store({
state: {
test: 3
}
});
describe('test.vue', () => {
it('should get test from store', () => {
const parent = new Vue({
template: '<div><test ref="test"></test></div>',
components: { test },
store: store
}).$mount();
expect(parent.$refs.test.name).toBe(3);
});
}
Note the "ref" this example doesn't work without it.
Is this really the right way to do this? It seems like it will get messy fast, because it requires adding the props into the template as a string.
Evan's quote seems to imply that the store can be added directly to the child component (ie not the parent like the example).
How do you do that?
The answer is actually really straightforward but not currently documented.
const propsData = { prop: { id: 1} };
const store = new Vuex.Store({state, getters});
const Constructor = Vue.extend(importedComponent);
const component = new Constructor({ propsData, store });
Note the store passed to the constructor. propsData is currently documented, the "store" option isn't.
Also if you are using Laravel, there are weird problems with the webpack versions you may be running.
The
[vuex] must call Vue.use(Vuex),
Error was caused by useing laravel-elixir-webpack-official.
If you did the fix:
npm install webpack#2.1.0-beta.22 --save-dev
for this https://github.com/JeffreyWay/laravel-elixir-webpack-official/issues/17
Your tests that include Vuex seem to break with the
[vuex] must call Vue.use(Vuex)
even when you have Vue.use(Vuex)
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();
});
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?