-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbrowser.js
117 lines (99 loc) · 3.19 KB
/
browser.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
module.exports = describe => {
describe(
() => weatherApp(),
describe(
app => app.whenTheWeatherServiceIsOnline(),
describe(
app => app.visitWeatherForecast(),
describe('shows the weather forecast returned by the service',
app => app.visibleMessage,
it => it.equals('The weather forecast')
)
)
),
describe(
app => app.whenTheWeatherServiceIsOffline(),
describe(
app => app.visitWeatherForecast(),
describe('shows a service unavailable warning',
app => app.visibleMessage,
it => it.equals('Please try again later')
)
)
)
)
}
// High-level tests communicate with the app under test via some driver
// (e.g. selenium/browser-monkey). From the perspective of the tests, this
// driver represents "the app". So this utility creates an element and passes
// it to an app-specific "driver" which will eventually instantiate the app
// with the element as an argument
function weatherApp () {
const element = document.createElement('div')
element.className = 'weather-app'
document.body.appendChild(element)
return new WeatherAppDriver({ element })
}
// the class under test represents a web app. It talks asynchonously to a
// service, then updates a DOM element, depending on the outcome
class WeatherApp {
constructor ({ element, weatherService }) {
this.element = element
this.weatherService = weatherService
}
showWeatherForecast () {
return this.weatherService.forecast()
.then(outlook => {
this.element.innerText = outlook
})
.catch(() => {
this.element.innerText = 'Please try again later'
})
}
}
// builders are useful for building up nested contexts in declarative tests
class Builder {
constructor (options) {
this.options = options
for (const key in options) { this[key] = options[key] }
}
with (options) {
return new this.constructor(Object.assign(this.options, options))
}
}
// Driving a web app in a specific context means:
// 1. building up the context (e.g. some service is online/offline)
// 2. interacting with the app (e.g. visit the weather forecast)
// 3. asserting about its final state (e.g. get the visible message)
class WeatherAppDriver extends Builder {
constructor (options) {
super()
this.app = new WeatherApp(options)
}
whenTheWeatherServiceIsOffline () {
return this.with({ weatherService: new OfflineWeatherService() })
}
whenTheWeatherServiceIsOnline () {
return this.with({ weatherService: new StubWeatherService() })
}
visitWeatherForecast () {
this.app.showWeatherForecast()
}
get visibleMessage () {
return this.element.innerText
}
}
// Use fakes to represent the interactions with external dependencies. Using
// FinishedPromise instead of Promise means we can keep our tests synchronous,
// while our application code is asynchronous (using real Promises)
const FinishedPromise = require('finished-promise')
class StubWeatherService {
forecast () {
return FinishedPromise.resolve('The weather forecast')
}
}
class OfflineWeatherService {
forecast () {
return FinishedPromise.reject(new Error('Connection is offline'))
}
}