Skip to content

Commit e5c358e

Browse files
OrKoNDevtools-frontend LUCI CQ
authored andcommitted
Update unit test guidance with model stubs
Change-Id: I1e28bf286f77acebed20a95859313ba0621e5199 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6276565 Commit-Queue: Alex Rudenko <[email protected]> Reviewed-by: Benedikt Meurer <[email protected]> Reviewed-by: Danil Somsikov <[email protected]>
1 parent 2bff7c0 commit e5c358e

File tree

1 file changed

+43
-2
lines changed

1 file changed

+43
-2
lines changed

docs/ui_engineering.md

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ class StylesPane extends UI.Widget {
8383
8484
### Unit tests
8585
86-
When testing presenter behavior await a view update or a model call:
86+
When testing presenters, rely on observable effects such as view updates or model calls.
87+
88+
#### View stubbing
8789
8890
```ts
8991
// ✅ recommended: stub the view function.
@@ -99,6 +101,7 @@ const [{onEvent}] = await viewCall;
99101
const viewCall2 = expectCall(view);
100102
onEvent();
101103
const [{data}] = await viewCall2;
104+
// ✅ recommended: check view input data from the expected call.
102105
assert.deepStrictEqual(data, {});
103106

104107
// ❌ not recommended: Widget.updateComplete only reports a current view update
@@ -110,5 +113,43 @@ assert.deepStrictEqual(view.lastCall.args[0], {});
110113
// ❌ not recommended: awaiting for the present logic to finish might
111114
// not account for async or throttled view updates.
112115
await presenter.doSomething();
113-
assert.deepStrictEqual(view.lastCall.args[0], {});
116+
// ❌ not recommended: it is easy for such assertions to
117+
// rely on the data not caused by the action being tested.
118+
sinon.assert.calledWith(view, sinon.match({ data: 'smth' }));
119+
```
120+
121+
#### Model stubbing
122+
123+
```ts
124+
// ✅ recommended: stub models that the presenter relies on.
125+
// Note there are many good ways to stub/mock models with sinon
126+
// depending on the use case and existing model code structure.
127+
const cssModel = sinon.createStubInstance(SDK.CSSModel.CSSModel);
128+
129+
const presenter = new Presenter();
130+
// ✅ recommended: expect model calls as the result of invoking
131+
// present's logic.
132+
const modelCall = expectCall(cssModel.headersForSourceURL, {
133+
fakeFn: () => {
134+
return false,
135+
},
136+
});
137+
// ✅ recommended: expect view calls to result in output based
138+
// on the mocked model.
139+
const viewCall = expectCall(view);
140+
141+
presenter.doSomething();
142+
143+
// ✅ recommended: assert arguments provided to model calls.
144+
const [url] = await modelCall;
145+
assert.strictEqual(url, '...');
146+
147+
const [{headersForSourceURL}] = await viewCall;
148+
assert.deepStrictEqual(headersForSourceURL, [{...}]);
149+
150+
// ❌ not recommended: mocking CDP responses to make the models behave in a certain way
151+
// is fragile.
152+
setMockConnectionResponseHandler('CSS.getHeaders', () => ({}));
153+
const presenter = new Presenter();
154+
presenter.doSomething();
114155
```

0 commit comments

Comments
 (0)