Unit Testing
Test individual methods or functions of the classes, components or modules used by your app. These are the least expensive tests and are the easiest to integrate into CI.
Integration Testing
Test individual units of your app as a group. Integration tests provide a balance between the speed of unit tests and the “real world” interactions of end-to-end tests.
End to End Testing
Test by replicating user behavior in a complete application environment and verify that user stories flow as expected. E2E tests bring the most confidence by validating the entire chain of system dependencies such as communicating with other systems, interfaces, databases, and networks.
Slow
It takes time to start new browser instance and load the page. It may take 20 seconds for one test, and with many tests executed in sequence, it goes up to minutes.
Hard to develop
Developing is difficult with a slow feedback loop and cryptic error messages.
Non-deterministic issues
A test is non-deterministic when it passes or fails randomly, without any noticeable change in the code or environment.
Brittle
Because these tests suffer from direct coupling with the user interface itself, they are more likely to break with the most minor UI changes.
The Happy Path
Happy path testing reflects the most essential user stories of your product. Starting with well-defined test cases helps you focus on the core UX scenarios where bugs are unacceptable. For exception scenarios, use integration testing and low-level unit testing.
Not a lot!
Keep end to end tests to a bare minimum. Google suggests a 70/20/10 split: 70% unit tests, 20% integration tests, and 10% end-to-end tests.
Define Scope
Decide what components are in scope for your app. Is it just the front end or the whole application stack?
Analyze
Decide how you will logically divide up the app
Write out each step from a user perspective
Selenium Based
Non Selenium Based
Use the Page Object Model pattern
A page object is a class that serves as an interface to a page of your app. It makes tests readable and increases maintainability. If a selector is changed, you only have to update it one place, not throughout your whole test suite.
// page model
export default class BaseViewModel {
constructor( {
// Top bar
this.linkFabric = `[data-test="link-fabric"]`
this.linkBreadcrumbs = `[data-test="link-breadcrumbs"]`
this.linkSettings = `[data-test="link-settings"]`
// Navigation
this.linkTabs = `[data-test="link-tabs"]`
// Internationalization
this.linkLanguages = `[data-test="link-languages"]`
this.linkLanguagesEn = `[data-test="link-languagesEN"]`
this.linkLanguagesEs = `[data-test="link-languagesES"]`
// Banner
this.textTitle = `[data-test="text-title"]`
// Footer bar
this.linkDecipher = `[data-test="link-decipher"]`
this.linkGitHub = `[data-test="link-github"]`
this.linkTwitter = `[data-test="link-twitter"]`
this.linkLinkedIn = `[data-test="link-linkedin"]`
}
}
// test file
const baseView = new BaseViewModel();
expect(baseView.textTitle).toBe("Fabric");
Use data attributes
data-* attributes keep the tests independent from changes in the CSS or JS (ex. button id=”main” class=”btn btn-large” data-test=”submit-login-btn”); this will make your tests more resilient.
Start each test suite from view you're testing
Always set the start url in the context of your test, this will lower the execution time of the test suites and avoid fails triggered by bugs in different parts of the application which are not to be tested by the respective test suite.
Don't sleep
Avoid waiting for arbitrary periods of time in your test. Instead use assertions that wait for a specific condition to be met; this will make the tests less flaky.
Text