You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
For testing different scenarios (200, 400, 404 status codes) we can use [axios-mock-adapter](https://www.npmjs.com/package/axios-mock-adapter), but there are also other alternatives available (e.g. [moxios](https://www.npmjs.com/package/moxios)).
44
+
```js
45
+
importaxiosfrom'axios';
46
+
importAxiosMockAdapterfrom'axios-mock-adapter';
47
+
48
+
beforeEach(function () {
49
+
this.axios=newAxiosMockAdapter(axios);
50
+
});
51
+
52
+
afterEach(function () {
53
+
this.axios.verifyNoOutstandingExpectation();
54
+
this.axios.restore();
55
+
});
56
+
57
+
it('should call external API with given params', function () {
* When defining mock, always try to set expected params or body (be as much specific as it gets). It prevents your tests going green because of the functionality under test was accidentally called from other parts of your code.
69
+
* Always specify the number of calls you expect. Use `replyOnce` when you know the external call should happen only once (99% test cases). If your code has a bug and calls API twice, you will spot the problem because the 2nd call will fail with `Error: Request failed with status code 404`.
70
+
* Always restore axios back to the state before your test was running. Use `axios.restore()` function to do so.
71
+
* Verify after each test that all registered mocks have been called using `axios.verifyNoOutstandingExpectation()`. It can catch issues when your test didn't executed part of the code you expected, but still went green. You can implement your own expectation if you need to, this is the current implementation:
expect.fail(1, 0, `Expected URL ${expectation[0]} to have been requested but it wasn't.`);
83
+
}
84
+
}
85
+
}
86
+
};
87
+
```
88
+
Dont's
89
+
* Do not mock original axios functions because you loose huge portion of functionality done by axios (e.g. custom interceptors).
90
+
91
+
```js
92
+
importaxiosfrom'axios';
93
+
importsinonfrom'sinon';
94
+
95
+
beforeEach(function () {
96
+
axios.get=sinon.stub(); // <- please don't
97
+
});
98
+
```
99
+
If you really need to check whether the `get` or `post` have been called, use rather `sinon.spy(axios, 'post');` so the original logic is preserved.
100
+
101
+
# Assert console.error() has not been called
102
+
103
+
Let's have a test expecting no action to happen (e.g. a disabled button that should not be enabled while an input is empty). The test loads the form and then it checks if the button is disabled. Everything goes green and everybody's happy. But there is a different problem your test didn't cover. The button was not kept disabled because of the application logic, but because of a javascript error and part of the code didn't execute. To catch cases like this ...
104
+
105
+
### Dos
106
+
* Make sure, after each test, that `console.error()` has not been called. You can spy on `error` function and verify expectations in `afterEach()`.
107
+
108
+
```js
109
+
importsinonfrom'sinon';
110
+
import { expect } from'chai';
111
+
112
+
beforeEach(function () {
113
+
if (console.error.restore) { // <- check whether the spy isn't already attached
114
+
console.error.restore(); // restore it if so. this might happen if previous test crashed the test runner (e.g. Syntax Error in your spec file)
115
+
}
116
+
sinon.spy(console, 'error'); // use only spy so the original functionality is still preserved, don't stub the error function
117
+
});
118
+
119
+
afterEach(function () {
120
+
expect(console.error, `console.error() has been called ${console.error.callCount} times.`).not.to.have.been.called;
121
+
console.error.restore(); // <- always restore error function to its initial state.
122
+
});
123
+
```
124
+
125
+
# Page objects pattern
126
+
127
+
If you are new to the page objects, please follow [great overview](https://martinfowler.com/bliki/PageObject.html) written by [Martin Fowler](https://martinfowler.com/). The basic idea is to keep things [DRY](https://code.tutsplus.com/tutorials/3-key-software-principles-you-must-understand--net-25161) and reusable, but there are few more things to mention: refactoring and readability. Let's have a page object like this:
128
+
```js
129
+
classLoginPage {
130
+
getusernameInput() {
131
+
returnfindById("username");
132
+
}
133
+
134
+
getpasswordInput() {
135
+
returnfindByCss("input.password");
136
+
}
137
+
138
+
getsubmitButton() {
139
+
returnfindByXPath("/form/div[last()]/button[@primary]"); // <- please don't do this ever
140
+
}
141
+
142
+
login(username, password) {
143
+
this.usernameInput.setValue(username);
144
+
this.passwordInput.setValue(password);
145
+
this.submitButton.click();
146
+
}
147
+
}
148
+
```
149
+
The example above has following issues:
150
+
* Every time an ID or CSS class change, you have to update the page object.
151
+
* If you want to remove ID from the username, you have to check whether is it used in tests or not.
152
+
* If you want to remove a CSS class, you have to check whether is it used in tests or not. If it's used in the test, you have to keep class assigned to the element but the class will no longer exist in CSS file. This causes many confusions.
153
+
* Every time a new object is added to the form, it breaks the XPath for the submit button. Because of it, using XPath instead of CSS selector is always considered a bad idea.
154
+
155
+
None of these is giving you the confidence to do minor/major refactoring of your CSS or HTML because you might break the tests. To remove this coupling, you can give a unique identifier for your element through `data-` attributes introduces in [HTML5](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes) or you can use your own attribute. You can choose a name of the attribute such as `data-test-id`, `data-qa` or simply `tid` if you prefer short names. The page object will look like then:
156
+
```js
157
+
classLoginPage {
158
+
getusernameInput() {
159
+
returnfindByCss("[tid='login__username']");
160
+
}
161
+
162
+
getpasswordInput() {
163
+
returnfindByCss("[tid='login__password']");
164
+
}
165
+
166
+
getsubmitButton() {
167
+
returnfindByCss("[tid='login__submit-button']");
168
+
}
169
+
170
+
login(username, password) {
171
+
this.usernameInput.setValue(username);
172
+
this.passwordInput.setValue(password);
173
+
this.submitButton.click();
174
+
}
175
+
}
176
+
```
177
+
In the next step you can make the selector little bit shorter by extracting part of the selector to another function:
178
+
```js
179
+
functionfindByTId(tid) {
180
+
returnfindByCss(`[tid*='${tid}']`);
181
+
}
182
+
```
183
+
Adding `*` into the selector allows us to assign more than one identifier to a single element and having better versatility in your page objects, e.g.:
184
+
```html
185
+
<nav>
186
+
<ul>
187
+
<litid="home nav-item nav-item-1">Home</li>
188
+
<litid="about nav-item nav-item-2">About</li>
189
+
<litid="help nav-item nav-item-3">Help</li>
190
+
</ul>
191
+
</nav>
192
+
```
193
+
Now you can select individual items by unique ID (`home`, `about`, `help`), or by index (`nav-item-{index}`) or you can select all of them (`nav-item`):
0 commit comments