Add debug logging to BaseService and BaseJsonService (#1867)

Invoke this using `npm run test:services:trace`.
This commit is contained in:
Paul Melnikow
2018-08-10 15:22:13 -04:00
committed by GitHub
parent ada1dec815
commit 0db88d33e0
6 changed files with 318 additions and 62 deletions

View File

@@ -13,6 +13,7 @@
'use strict'
const config = require('./test-config')
const serverConfig = require('./server-config')
let startCalled = false
@@ -31,13 +32,13 @@ function start() {
}
startCalled = true
const originalArgv = process.argv
// Modifying argv during import is a bit dirty, but it works, and avoids
// making bigger changes to server.js.
process.argv = ['', '', config.port, 'localhost']
// Modifying config is a bit dirty, but it works, and avoids making bigger
// changes to server.js.
serverConfig.bind = {
host: 'localhost',
port: config.port,
}
const server = require('../server')
process.argv = originalArgv
return server
}

View File

@@ -60,6 +60,7 @@ const config = {
intervalSeconds: process.env.GITHUB_DEBUG_INTERVAL_SECONDS || 300,
},
},
trace: envFlag(process.env.TRACE_SERVICES),
},
font: {
path: process.env.FONT_PATH || defaults.font.path,

232
package-lock.json generated
View File

@@ -378,8 +378,7 @@
"acorn": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz",
"integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=",
"optional": true
"integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc="
},
"acorn-dynamic-import": {
"version": "2.0.2",
@@ -756,6 +755,27 @@
"chalk": "^1.1.3",
"esutils": "^2.0.2",
"js-tokens": "^3.0.2"
},
"dependencies": {
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
}
}
},
"babel-core": {
@@ -2237,6 +2257,15 @@
}
}
},
"camelo": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/camelo/-/camelo-1.1.11.tgz",
"integrity": "sha512-kYyJyTKwCcKXtWMNqHogVBgQMRArxK0sLcPaMgDteo4vHr5vRytzBZt6n2T81RrQIU9FPi4MW8X+snzMDQqH0w==",
"requires": {
"regex-escape": "^3.3.0",
"uc-first-array": "^1.0.0"
}
},
"camp": {
"version": "17.2.1",
"resolved": "https://registry.npmjs.org/camp/-/camp-17.2.1.tgz",
@@ -2334,16 +2363,23 @@
}
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "^1.9.0"
}
}
}
},
"chardet": {
@@ -2491,8 +2527,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@@ -2513,14 +2548,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -2535,20 +2568,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -2665,8 +2695,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@@ -2678,7 +2707,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -2693,7 +2721,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -2701,14 +2728,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@@ -2727,7 +2752,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -2808,8 +2832,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -2821,7 +2844,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@@ -2907,8 +2929,7 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -2944,7 +2965,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -2964,7 +2984,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -3008,14 +3027,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},
@@ -3239,7 +3256,6 @@
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz",
"integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=",
"dev": true,
"requires": {
"color-name": "^1.1.1"
}
@@ -3247,8 +3263,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"colors": {
"version": "1.1.2",
@@ -3725,8 +3740,7 @@
"cssom": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz",
"integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=",
"optional": true
"integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs="
},
"cssstyle": {
"version": "0.2.37",
@@ -4300,6 +4314,22 @@
"integrity": "sha1-WUjLKG8uSO3DslGnz8H3iDOW1lw=",
"dev": true
},
"emojic": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/emojic/-/emojic-1.1.14.tgz",
"integrity": "sha512-n6yEaHU9GB6AfHTDAvV/263Ds+nIvboyiYYrAOuZox64RP6AlXPW1jbLsT3cBX+8hRVc7IV3P59ODPr2bC0OJA==",
"requires": {
"camelo": "^1.0.0",
"emojilib": "^2.0.2",
"iterate-object": "^1.2.0",
"r-json": "^1.1.0"
}
},
"emojilib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.3.0.tgz",
"integrity": "sha512-gDrDiITC7W5lvXTtUmk0k9soizsjfY9Wqr2GWsEMX7+cKChoxLt3RnT8zW32BvN1SFDQbi7+D5/+HNu/R+qvKA=="
},
"emojis-list": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
@@ -5655,6 +5685,27 @@
"chalk": "^1.1.3",
"error-stack-parser": "^2.0.0",
"string-length": "^1.0.1"
},
"dependencies": {
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
}
}
},
"from2": {
@@ -6853,6 +6904,11 @@
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"iterate-object": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/iterate-object/-/iterate-object-1.3.2.tgz",
"integrity": "sha1-JOwVr/pdADnog5aVohwsrh9Ftms="
},
"jison": {
"version": "0.4.13",
"resolved": "https://registry.npmjs.org/jison/-/jison-0.4.13.tgz",
@@ -8778,7 +8834,6 @@
"version": "0.1.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"kind-of": "^3.0.2",
"longest": "^1.0.1",
@@ -9809,8 +9864,7 @@
"longest": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"loose-envify": {
"version": "1.3.1",
@@ -11765,6 +11819,11 @@
}
}
},
"r-json": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/r-json/-/r-json-1.2.8.tgz",
"integrity": "sha1-dEBWDMHt8AudjZT6MLytfd6U6uI="
},
"ramda": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz",
@@ -12153,6 +12212,11 @@
"private": "^0.1.6"
}
},
"regex-escape": {
"version": "3.4.8",
"resolved": "https://registry.npmjs.org/regex-escape/-/regex-escape-3.4.8.tgz",
"integrity": "sha512-DG0VFPTDwfl+XsuVaM/0RmGJvCpZNB9UG/limzbev50XQ44G4mbOG+0Eh5M7Al9JB68NbP7YeY1KhDzpnX7qSw=="
},
"regex-not": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
@@ -13618,10 +13682,19 @@
"dev": true
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"requires": {
"has-flag": "^3.0.0"
},
"dependencies": {
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
}
}
},
"supports-hyperlinks": {
"version": "1.0.1",
@@ -14121,6 +14194,19 @@
"integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==",
"dev": true
},
"uc-first-array": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/uc-first-array/-/uc-first-array-1.1.8.tgz",
"integrity": "sha512-LTAnb8G/BxmXyBqiUxRhlbYt+IPCCpAOJJsC3xp5cwtXZKdtBfBxAMWRJlNzknM+Gpw3DRD2K3sQ64srroRf3w==",
"requires": {
"ucfirst": "^1.0.0"
}
},
"ucfirst": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/ucfirst/-/ucfirst-1.0.0.tgz",
"integrity": "sha1-ThBbZEjQXiZOzsQ14LkZNjxfLy8="
},
"uglify-es": {
"version": "3.3.9",
"resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
@@ -14496,6 +14582,27 @@
"requires": {
"chalk": "^1.1.1",
"object-assign": "^4.0.1"
},
"dependencies": {
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
}
}
},
"verror": {
@@ -14892,6 +14999,19 @@
"moment": "^2.11.2"
},
"dependencies": {
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -14906,6 +15026,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
}
}
},

View File

@@ -23,8 +23,10 @@
},
"dependencies": {
"camp": "~17.2.1",
"chalk": "^2.4.1",
"chrome-web-store-item-property": "~1.1.2",
"dot": "~1.1.2",
"emojic": "^1.1.14",
"escape-string-regexp": "^1.0.5",
"glob": "^7.1.1",
"gm": "^1.23.0",
@@ -69,6 +71,7 @@
"test:js:server": "HANDLE_INTERNAL_ERRORS=false mocha \"*.spec.js\" \"lib/**/*.spec.js\" \"services/**/*.spec.js\"",
"test:integration": "mocha \"services/**/*.integration.js\"",
"test:services": "HANDLE_INTERNAL_ERRORS=false mocha --delay lib/service-test-runner/cli.js",
"test:services:trace": "TRACE_SERVICES=true npm run test:services -- $*",
"test:services:pr:prepare": "node lib/service-test-runner/pull-request-services-cli.js > pull-request-services.log",
"test:services:pr:run": "HANDLE_INTERNAL_ERRORS=false mocha --delay lib/service-test-runner/cli.js --stdin < pull-request-services.log",
"test:services:pr": "npm run test:services:pr:prepare && npm run test:services:pr:run",

View File

@@ -1,6 +1,9 @@
'use strict'
const Joi = require('joi')
// See available emoji at http://emoji.muan.co/
const emojic = require('emojic')
const chalk = require('chalk')
const { NotFound, InvalidResponse, Inaccessible } = require('./errors')
const queryString = require('query-string')
const {
@@ -10,6 +13,14 @@ const {
setBadgeColor,
} = require('../lib/badge-data')
const { checkErrorResponse, asJson } = require('../lib/error-helper')
// Config is loaded globally but it would be better to inject it. To do that,
// there needs to be one instance of the service created at registration time,
// which gets the config injected into it, instead of one instance per request.
// That way most of the current static methods could become instance methods,
// thereby gaining access to the injected config.
const {
services: { trace: enableTraceLogging },
} = require('../lib/server-config')
class BaseService {
constructor({ sendAndCacheRequest }, { handleInternalErrors }) {
@@ -141,10 +152,20 @@ class BaseService {
}
async invokeHandler(namedParams, queryParams) {
const logTrace = (...args) => this.constructor.logTrace(...args)
logTrace(
'inbound',
emojic.womanCook,
'Service class',
this.constructor.name
)
logTrace('inbound', emojic.ticket, 'Named params', namedParams)
logTrace('inbound', emojic.crayon, 'Query params', queryParams)
try {
return await this.handle(namedParams, queryParams)
} catch (error) {
if (error instanceof NotFound) {
logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
return {
message: error.prettyMessage,
color: 'red',
@@ -153,18 +174,36 @@ class BaseService {
error instanceof InvalidResponse ||
error instanceof Inaccessible
) {
logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
return {
message: error.prettyMessage,
color: 'lightgray',
}
} else if (this._handleInternalErrors) {
console.log(error)
if (
!logTrace(
'unhandledError',
emojic.boom,
'Unhandled internal error',
error
)
) {
// This is where we end up if an unhandled exception is thrown in
// production. Send the error to the logs.
console.log(error)
}
return {
label: 'shields',
message: 'internal error',
color: 'lightgray',
}
} else {
logTrace(
'unhandledError',
emojic.boom,
'Unhandled internal error',
error
)
throw error
}
}
@@ -232,6 +271,7 @@ class BaseService {
namedParams,
queryParams
)
this.logTrace('outbound', emojic.shield, 'Service data', serviceData)
const badgeData = this._makeBadgeData(queryParams, serviceData)
// Assumes the final capture group is the extension
@@ -241,6 +281,31 @@ class BaseService {
})
)
}
static _formatLabelForStage(stage, label) {
const colorFn = {
inbound: chalk.black.bgBlue,
fetch: chalk.black.bgYellow,
validate: chalk.black.bgGreen,
unhandledError: chalk.white.bgRed,
outbound: chalk.black.bgBlue,
}[stage]
return colorFn(` ${label} `)
}
static logTrace(stage, symbol, label, ...content) {
if (enableTraceLogging) {
console.log(
this._formatLabelForStage(stage, label),
symbol,
'\n',
...content
)
return true
} else {
return false
}
}
}
class BaseJsonService extends BaseService {
@@ -250,29 +315,47 @@ class BaseJsonService extends BaseService {
stripUnknown: true,
})
if (error) {
this.logTrace(
'error',
emojic.womanShrugging,
'Response did not match schema',
error.message
)
throw new InvalidResponse({
prettyMessage: 'invalid json response',
underlyingError: error,
})
} else {
this.logTrace('validate', emojic.bathtub, 'JSON after validation', value)
return value
}
}
async _requestJson({ schema, url, options = {}, notFoundMessage }) {
const logTrace = (...args) => this.constructor.logTrace('fetch', ...args)
if (!schema || !schema.isJoi) {
throw Error('A Joi schema is required')
}
return this._sendAndCacheRequest(url, {
const mergedOptions = {
...{ headers: { Accept: 'application/json' } },
...options,
})
}
logTrace(emojic.bowAndArrow, 'Request', url, '\n', mergedOptions)
return this._sendAndCacheRequest(url, mergedOptions)
.then(({ res, buffer }) => {
logTrace(emojic.dart, 'Response status code', res.statusCode)
return { res, buffer }
})
.then(
checkErrorResponse.asPromise(
notFoundMessage ? { notFoundMessage: notFoundMessage } : undefined
)
)
.then(asJson)
.then(json => {
logTrace(emojic.dart, 'Response JSON (before validation)', json)
return json
})
.then(json => this.constructor._validate(json, schema))
}
}

View File

@@ -83,6 +83,48 @@ describe('BaseService', () => {
expect(serviceData).to.deep.equal({ message: 'Hello bar.bar.bar!' })
})
describe('Logging', function() {
let sandbox
beforeEach(function() {
sandbox = sinon.createSandbox()
})
afterEach(function() {
sandbox.restore()
})
beforeEach(function() {
sinon.stub(DummyService, 'logTrace')
})
it('Invokes the logger as expected', async function() {
const serviceInstance = new DummyService({}, defaultConfig)
await serviceInstance.invokeHandler(
{
namedParamA: 'bar.bar.bar',
},
{ queryParamA: '!' }
)
expect(DummyService.logTrace).to.be.calledWithMatch(
'inbound',
sinon.match.string,
'Service class',
'DummyService'
)
expect(DummyService.logTrace).to.be.calledWith(
'inbound',
sinon.match.string,
'Named params',
{
namedParamA: 'bar.bar.bar',
}
)
expect(DummyService.logTrace).to.be.calledWith(
'inbound',
sinon.match.string,
'Query params',
{ queryParamA: '!' }
)
})
})
describe('Error handling', function() {
it('Handles internal errors', async function() {
const serviceInstance = new DummyService(