Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add e2e testing style guidelines #29

Merged
merged 3 commits into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.idea/
94 changes: 79 additions & 15 deletions javascript/testing/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
# Nielsen Testing Style Guide

This is the Nielsen Marketing Cloud Engineering team's style guide on web front-end testing.
> **Note**: The examples in this guide are using the [React](https://reactjs.org/) user interface library, in combination with [Jest](https://jestjs.io/) testing framework. We will share our opinions on different libraries for testing `React` components (Like [react-testing-library](https://github.com/testing-library/react-testing-library) which we also use for examples). However, we believe that our suggested approach could be well used with other frameworks/libraries with some slight adjustments
> **Note**: The examples in this guide are using the [React](https://reactjs.org/) user interface library, in combination with [Jest](https://jestjs.io/) testing framework. We will share our opinions on different libraries for testing `React` components (Like [react-testing-library](https://github.com/testing-library/react-testing-library) which we also use for examples). For End-to-end testing we use [Cypress](https://www.cypress.io/). However, we believe that our suggested approach could be well used with other frameworks/libraries with some slight adjustments

## Table of Contents

- [Nielsen Jest Style Guide](#nielsen-jest-style-guide)
- [Nielsen Testing Style Guide](#nielsen-testing-style-guide)
- [Basic Rules](#basic-rules)
1. [AAA Pattern - Arrange Act Assert](#aaa-pattern---arrange-act-assert)
1. [Act in React](#act-in-react)
1. [Async side effects in react](#async-side-effects-in-react)
- [React Component](#react-component)
1. [Hooks Component](#hooks-component)
1. [Class Component](#class-component)
1. [Redux Connected Component](#redux-connected-component)
- [Custom Hooks](#custom-hooks)
- [Proper use of `Date`](#proper-use-of-date)
- [Unit Testing](#unit-testing)
- [React Component](#react-component)
1. [Hooks Component](#hooks-component)
1. [Class Component](#class-component)
1. [Redux Connected Component](#redux-connected-component)
- [Custom Hooks](#custom-hooks)
- [Proper use of `Date`](#proper-use-of-date)
- [End-to-end Testing](#end-to-end-testing)
- [Find us](#find-us)
- [Contributors](#contributors)
- [Amendments](#amendments)
Expand Down Expand Up @@ -160,15 +162,17 @@ test("should render 1", () => {
});
```

## React Component
## Unit Testing

### React Component
We strongly encourage testing components' behavior rather than testing their implementation details. What does it mean? Generally, testing the behavior means making assertions about the outcome of a desired action, rather than asserting about the way the component achieved this outcome internally.

This means we can rewrite/refactor a component implementation and have its tests remain the same and not break.
If the test is tied to the way the component is implemented (e.g. by calling an internal function directly), changes in the implementation (e.g. renaming that function) would break the test even though the component still behaves the same.

>**Note**: Using `React Testing Library` helps us avoid writing implementation details tests, as opposed to `Enzyme` where it's much easier to test implementation details unintentionally.

### Hooks Component
#### Hooks Component

React hooks components are great to show why behavior testing is the way to go. The reason for it is that until lately we were used to only write class components, which are easy to test using implementation details tests.
This testing pattern will need change drastically in order to fit new functional components using hooks.
Expand Down Expand Up @@ -235,7 +239,7 @@ test('Shows the correct amount of clicks', () => {
This above "Don't" example uses Enzyme's `shallow()` and `wrapper.instance()`. It asserts about the 'count' prop in the state which is an implementation detail.
This test passes when we render `<ClickCounterClass />`, but it fails when we change it to render `<ClickCounterHooks />`

### Class Component
#### Class Component

When testing the async functionality of a `Class Component`, we want to ensure that our side effect finished running before asserting.
This is important because we should only assert after we are sure all changes to the DOM were made.
Expand Down Expand Up @@ -357,7 +361,7 @@ test('login - implementation details', async () => {
})
```

### Redux Connected Component
#### Redux Connected Component

When testing the integration between a `Connected Component` and its `React Component`, we want to ensure that any change in the `React Component` interface (i.e. props) will cause the tests to fail.
The idea is to create a small compatability test between a `React Component` and `Connected Component` (which connects it with redux).
Expand Down Expand Up @@ -419,7 +423,7 @@ describe('redux connected component unit-test', () => {
})
```

## Custom Hooks
### Custom Hooks

Sometimes, we need to create custom hooks that encapsulate relevant logic for multiple developers on our team.

Expand Down Expand Up @@ -459,7 +463,7 @@ test('should toggle state on off', () => {
```
Don't forget to wrap every interaction with `act`, so after the interaction finishes, you'll get the updated value!

## Proper use of `Date`
### Proper use of `Date`

We want to mock the `Date` object itself, this way we know for sure that we will get the exact result for the given date any time. In addition, we ensure that our current timestamp will always return the same result instead of creating a new timestamp every test, which could lead to false negatives.

Expand Down Expand Up @@ -487,6 +491,65 @@ jest.mock('moment', ()=> ({
}))
```

## End-to-end Testing
> For End-to-end testing we use [Cypress](https://www.cypress.io/) Framework. However, our suggested concepts could be adopted with other End-to-End frameworks/test runners.

marianna-exelate marked this conversation as resolved.
Show resolved Hide resolved
The below diagram illustrates main blocks that involved in End-to-end test.

![End-to-end Test diagram](./resources/e2eDiagram.png)

- **Test**
- Uses the [AAA pattern](#aaa-pattern---arrange-act-assert)
- One and only one unit that __has assertions__ for testing components' integration and data integrity
- Uses one or more `Page Objects` to access and interact with tested application page

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uses one or more Flows to implement "act" part of the test.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should start with PageObject, then Flow and only then with Test, which uses Page Object and Flow?


Here is a simple example of test that checks search data functionality:

``` javascript
import TestPageObject from './TestPageObject'
const testPageObject = new TestPageObject()

it('should search data correctly', () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that in this test we are actually checking that searchTerm was displayed and not that data was searched correctly.
Or we need to change the assertion (which should check that there are corrected filtered elements) or change the title of the test (which I prefer more because don't want to make an example more complicated)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it checks filter by search term functionality, since the last assertion is getSearchResults().should('contain', 'searchTerm') - that checks search functionality


testPageObject.typeSearchTerm('searchTerm')

testPageObject.getSearchResults().should('contain', 'searchTerm')
})
```

- **Page Object**

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can give a link for some article about Page Objects, for example, https://martinfowler.com/bliki/PageObject.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree

- Responsible to encapsulate technical details (like css selectors, data attributes, etc`) to access the elements of the tested application page

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"to access and manipulate", otherwise it seems that Page Object should be only getter specific

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

- Provides an atomic API for interaction with tested application page. This API is used by `Test`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Provides an API for atomic interactions with the tested application page" - I think that the operation should be atomic and not API

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

- Has no assertions
- Can use another Page Object, it depends on the hierarchical structure of application pages
- Can optionally an use *`Selectors`* module, that contains reusable css selectors definitions
marianna-exelate marked this conversation as resolved.
Show resolved Hide resolved
- Complex shared components should expose Page Objects for other consumers

Example:
``` javascript
class TestPageObject {

getSearchInput () {
return cy.get('[data-test-id="search-input"]')
}

typeSearchTerm (searchTerm) {
this.getSearchInput().type(searchTerm)
}

getSearchResults () {
return cy.get('[data-test-id="search-results-table"]')
}
}
```
- **Flow**
- Common reusable logic that can be shared between tests (i.e login flow)
- Uses one or more Page Objects under the hood
- Has no assertions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be good to give an example of Flow




## Find us

- We have a Tech Blog! You can find it at [Medium](https://medium.com/nmc-techblog).
Expand All @@ -505,4 +568,5 @@ Feel free to submit pull requests and contribute to this style guide!

Copyright (c) 2020 Nielsen

**[⬆ back to top](#nielsen-jest-style-guide)**
**[⬆ back to top](#nielsen-testing-style-guide)**

Binary file added javascript/testing/resources/e2eDiagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.