Skip to content

Commit

Permalink
📝 philosophy documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
FBerthelot committed Jun 21, 2019
1 parent 02907e4 commit 01c6017
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 18 deletions.
18 changes: 11 additions & 7 deletions website/docs/concept.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
title: Documentation
---

## Main concept of testing component
Component-test-utils is an opinionated test framework.

Unit testing component can be hard.
## Why should you use it ?

First thing to do is to understand what a component is.
It mocks your favorite framework in a way that allows to write sustainable and functional oriented tests of your components.

// TODO illustration of a component.
As component-test-utils consider component as a black box, you won't be able to directly access to the state of your components.
It will force you to make assertion on the generated view.

When testing a component consider to expect change when changing parameter.
Component internal state never have to be accessed directly, you must always do check on HTML.
When you want test internal state, you only want to test the behavior of the component, so let's trigger some events on the interface !
## My Favorite framework is supported ?

- _React_: Yes !
- ... more is coming

That's all at the moment. If you want your framework in this list consider contributing to this project [on github](https://github.com/FBerthelot/component-test-utils) :)
219 changes: 209 additions & 10 deletions website/docs/philosophy.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,219 @@
title: Philosophy about component testing
---

## How to test component ?
## Philosophy of testing

Before answer to this question, we need to answer to the question: "what is a component ?".
Before introducing the way you can test your front-end application, we need to share the same definitions.

A component is a Custom HTML Component that produce a view (aka. a piece of others HTML Component).
To produce the view Component take properties and his internal state.
## Types of tests

To sum up, a component is like a JavaScript function that take two arguments, the state and the props.
Component-test-utils assumes these approximative definitions well-fitted for component testing:

It's ineffective to try change the state of the component manually in test.

## What is the kind of test I can do with component-test-utils ?
- _Unit Test_: Test that stay in memory. (No access to: HTTP, File System, ...)
- _Integration test_: Test that can access to other things than memory (Network, I/O).
- _End to End test_: It's basically an integration test with a special rule. No Mock or Stub allowed.

You can do both unit and integration test with component test-utils.
The only test type you cannot do is end-to-end testing because component-test-util rely on some mocking solution.
The only test type you cannot do is end-to-end testing because component-test-utils is basically a mock of your favorite framework.

### Component take props as input

No matter the JavaScript framework (Vanilla, Vue.js, Angular, React, ...) you use, they share some common vision of what a component should be.

A component is a Custom HTML element that produces a view (aka. a piece of others Component and HTML elements).

![Component that generate view](/img/component_view.gif)

This component can receive parameters just like a function. Usually those parameters are called properties or props.

![Component that generate view according props](/img/component_props.gif)

Those props can produce a different view according to the props value.

As the render only depends on the props, no matter the framework you use, the easiest is to write tests to assert the view is rendered according to the props you give.

This can easily be achieved with component-test-utils. For example, if you consider testing a like button:

```js
describe('like button component', () => {
it('should render a button with "1 like" when props give 1 like', () => {
const component = shallow(Component, {props: {nbLikes: 1}});

expect(component.html()).toContain('1 like');
});

it('should render a button with "2 likes" when props give 2 likes', () => {
const component = shallow(Component, {props: {nbLikes: 2}});

expect(component.html()).toContain('2 likes');
});
});
```

### Statefull component

Unfortunately, components can sometimes be a bit more complex. For example when they have their own state.
Considering the previous example, imagine the like button has different styles depending on whether or not the user have liked the content related.

As component-test-utils consider component as a black box, you cannot modify or access its internal state.

Instead, you have to trigger an event from the view itself !

![Statefull component](/img/component_state.gif)

For example, if you want to test the different styles of your like button :

```javascript
describe('like button style', () => {
it('should set button to notLiked by default', () => {
const component = shallow(Component, {props: {nbLikes: 1}});

component.querySelector('button').dispatchEvent('click');

expect(component.html().querySelector('button').props.class).not.toContain(
'liked'
);
});

it('should set button to liked when clicking on the button', () => {
const component = shallow(Component, {props: {nbLikes: 1}});

component.querySelector('button').dispatchEvent('click');

expect(component.html().querySelector('button').props.class).toContain(
'liked'
);
});
});
```

### Component, event output

**Important: React being the only rendering library supported for now, these features are not implemented yet as they're not needed in react**

Given the component sometimes need to talk with parent component, frameworks use event to achieved that.

![Component that emit event](/img/component_event.png)

To ensure your component emit the good event, you can give spies to components and ensure they are called.

```javascript
describe('like button - onLike event', () => {
it('should emit onClick Event', () => {
const spy = createSpy();
// const spy = jest.fn(); using jest
const component = shallow(Component, {events: {onLike}});

component.querySelector('button').dispatchEvent('click');

expect(spy).toHaveBeenCalled();
});
});
```

### Component with multiple props

**Important: React being the only rendering library supported for now, these features are not implemented yet as they're not needed in react**

For example, Angular component can inject services in the constructor of the components.

To inject services, mixins, etc, an external key to shallow configuration object is available. The content of externals object is specific to the framework you are testing. It give you the opportunity to provide some non-standardized data to a component.

This part is the only framework specific thing you will have to learn for testing with component-test-utils !

![Component with externals](/img/component_externals.png)

An example :

```javascript
describe('user component', () => {
it('should display the user got from the service', () => {
const component = shallow(Component, {
externals: {
userService: {
getUser: () => Promise.resolve({name: 'component-test-utils'})
}
}
});

expect(component).toContain('component-test-utils');
});
});
```

### Mocking strategies

Component doesn't only generate HTML element, they can have sub-components in their view. For this kind of "parent" component, this is the main question the developer should ask: "should I mock this child component ?".

For this use case, Component-test-utils has two ways to create a component in a test environment:

- _White list_: Every sub-component is mocked, you can give a list a component that won't be mocked and give their mock.
- _Black list_: No sub-component is mocked, you can specify which component should be mocked.

Given these components:

```javascript
const postListRender = (posts) => `
<div>
${posts.map(postData => `<Post data={postData}/>`)}
<OtherComponent />
</div>
`;

const postRender = (post) => `
<article>
<h1>${post.title}</h1>
<p>${post.content}</p>
</article>
`;
```

#### white list (default)

```javascript
const cmp = shallow(postListRender, {
props: [
{title: 'post1', content: 'content1'},
{title: 'post2', content: 'content2'},
],
mocks: {
OtherComponent: `<div>OtherComponent</div>`
}
});

cmp.html() === `
<div>
<Post />
<Post />
<div>OtherComponent</div>
</div>
`
```

#### black list

```javascript
const cmp = shallow(postListRender, {
props: [
{title: 'post1', content: 'content1'},
{title: 'post2', content: 'content2'},
],
mocks: {
OtherComponent: false
}
});

To have more information on testing, you can check these slides (in french) : https://slides.com/florentberthelot/test-composants-web#/
cmp.html() === `
<div>
<article>
<h1>post1</h1>
<p>content1</p>
</article>
<article>
<h1>post2</h1>
<p>content2</p>
</article>
<OtherComponent />
</div>
`
```
3 changes: 3 additions & 0 deletions website/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
},
"shallow/setProps": {
"title": "shallow/setProps"
},
"shallow/unmount": {
"title": "shallow/unmount"
}
},
"links": {
Expand Down
1 change: 0 additions & 1 deletion website/pages/en/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ class HomeSplash extends React.Component {
<Button href={docUrl('getting-started.html')}>
Getting Started
</Button>
<Button href={docUrl('api.html')}>Doc</Button>
</PromoSection>
</div>
</SplashContainer>
Expand Down
1 change: 1 addition & 0 deletions website/static/img/component_draw.io.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<mxfile modified="2019-05-26T19:43:46.442Z" host="www.draw.io" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" version="10.6.9" etag="XsYau_xL1lRllDoklxSB" type="device"><diagram id="6480d411-75ee-57c6-0e92-c37b69faa278" name="Page-1">7VnbctowEP0aHjsDlm3MI1CSTqftpJOZdpI3YS+2GmE5srj16ytjyTfZCSFA2kl5AR/JK2v37NmV6aHpcnvNcRJ9ZQHQntUPtj30sWdZg4Hjya8M2eXIcOjkQMhJoCaVwC35DQrsK3RFAkhrEwVjVJCkDvosjsEXNQxzzjb1aQtG66smOAQDuPUxNdGfJBBRjnr9fol/AhJGamWkB+bYfwg5W8VquZ6FFvtPPrzE2pSan0Y4YJsKhGY9NOWMifzXcjsFmrlWey2/76pj9PFO3CfJ1b1Frc/fd3fzlTP+9kHHYo3pSm1vzngAXD2C2OldU+Y/QGZq0EMTvVMOsTh+7ZGxtrHq3l3FspuICLhNsJ+NbiS7JBaJJVXDhcP68iKkOE3Vb58tia9xjgMiH3vKKOMSi1kMxY7WwAVsG2EuN/mEC9W+r4EtQfCdvFGbQSqciu2OutyU1HE1Q6IKbayhArGia1iYLh0tfyhft/t9YEYXp3Da2FrGGlO2TKRX5e2WS6WNyVzSyQ3F3vhxSCct+s/TguI50EmRfI3Ap4KzB9CgzEnX92C+KEZ0issQTxaE0srMAIO38DOcxULp1MBS1638qnp10EG5TiYhq86kgWtSqcCqVBpYJ6BSO/dtI/hrAptLiIdjLAKBlGl1ybiIWMhiTGclOqnTppzzhbFEPdwvEGKnYolXgtWplK+ZLfRCjbDbI8uBYkHWdWttUVK33jCSZZVmhO3VGWH1G4FO2Yr7oO6qurhhyNFa3GVIYB6CMAyNOce7yrQkm5AatCp2/Aqmuf9l5mCZOaqMdVD0LxafoUGJH23ic84oetYcue5BUXTAC+yWKHY0JLVmptrAnC3EjtsIsdcSYrslxN65IozM3mJDRHTDWZJeoMYg+1+qMdpZueY/NRGdpRg1FeLQGnKqCpFvq0aWjCgyTwiYbJHJIxqnh1puqyysJrGCMCVhLC99GQ55TkKTLBWJPB6O1cCSBMGeCm1SU6fHQSeW4xM+p0T30aSZ8CMz4YdtR5Oz5btjhDAVWMBiRS+R7maLoXuETLP3bxS0nLuPK5a3DPIM78pPFcq7idvsybUB+Si5jY5O433Q0Xm6xWh2s8ikY1v5ORsdbbP8mAUhDsbZO6UsApnDMjedRM07fFXxhdPiC429UsubrUDh95ceLIwTStPQmYuCbRYFWIM+o5xXT2zzcHwx+tgdNf4y9DFagWPpY6E3po9ZkGaKPs8eHt+FpHfxTJtxnpf00UUl3XznLMMEXDbol5AE7Y43kYTRW0qC3a8zYXRsQWnYKXrYCymCY75Sn3US6H1IQAevdC1otBIt74280yiAvCz/HMvjXf4BiWZ/AA==</diagram></mxfile>
Binary file added website/static/img/component_event.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website/static/img/component_externals.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website/static/img/component_props.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website/static/img/component_state.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website/static/img/component_view.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 01c6017

Please sign in to comment.