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 74f0cdd
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 19 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.
Because component-test-utils consider each component as a black box, you won't be able to get access to your components state.
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) :)
224 changes: 214 additions & 10 deletions website/docs/philosophy.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,224 @@
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 each props value.

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

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 the props `nbLikes` is set to 1', () => {
const component = shallow(Component, {props: {nbLikes: 1}});

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

it('should render a button with "2 likes" when the props `nbLikes` is set to 2', () => {
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 related content.

Because component-test-utils considers each component as a black box, you cannot modify or access their internal state.

Instead, you have to trigger an internal 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 currently supported, these features are not implemented yet as they're not needed in react**

Because components sometimes need to talk with parent components, frameworks usually use an event based system to setup an upward communication channel between components and their parents.

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

To ensure your component emit the right event, you can attach spies to components and test if they have been 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 externals

**Important: React being the only rendering library currently supported, 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 the [shallow configuration object](/shallow/constructor#options) is available. The content of externals object is specific to the framework you are testing. It gives 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 retrieves from the service', () => {
const component = shallow(Component, {
externals: {
userService: {
getUser: () => Promise.resolve({name: 'component-test-utils'})
}
}
});

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

### Mocking strategies

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

In this case, Component-test-utils provides 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 = ({data}) => `
<article>
<h1>${data.title}</h1>
<p>${data.content}</p>
</article>
`;
```

#### White list (default)

```javascript
const cmp = shallow(postListRender, {
props: {
posts: [
{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: {
posts: [
{title: 'post1', content: 'content1'},
{title: 'post2', content: 'content2'},
]
},
mocks: {
OtherComponent: false
},
blackList: true
});

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
2 changes: 1 addition & 1 deletion website/siteConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const users = [

const siteConfig = {
title: 'Component test utils',
tagline: 'Test component in stadardized way',
tagline: 'Test component in standardized way',
url: 'https://component-test-utils.berthelot.io',
cname: 'component-test-utils.berthelot.io', // Your website URL
baseUrl: '/', // Base URL for your project */
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 74f0cdd

Please sign in to comment.