Repository: karma-runner/karma
Pull request #3628 (2021-01-22 00:14:15)

fix(plugins): refactor instantiatePlugin from preproprocessor

AppVeyorBot: :white_check_mark: [Build karma 2874 completed](https://ci.appveyor.com/project/dignifiedquire/karma/builds/37388515) (commit https://github.com/karma-runner/karma/commit/d2d38a109d by @johnjbarton)
karmarunnerbot: :white_check_mark: [Build karma 477 completed](https://ci.appveyor.com/project/karmarunnerbot/karma/builds/37388514) (commit https://github.com/karma-runner/karma/commit/d2d38a109d by @johnjbarton)
karmarunnerbot: :white_check_mark: [Build karma 476 completed](https://ci.appveyor.com/project/karmarunnerbot/karma-8n04t/builds/37388516) (commit https://github.com/karma-runner/karma/commit/d2d38a109d by @johnjbarton)
johnjbarton: I need a unit test for the new function.
AppVeyorBot: :white_check_mark: [Build karma 2882 completed](https://ci.appveyor.com/project/dignifiedquire/karma/builds/37443749) (commit https://github.com/karma-runner/karma/commit/1665e403a3 by @johnjbarton)
karmarunnerbot: :white_check_mark: [Build karma 485 completed](https://ci.appveyor.com/project/karmarunnerbot/karma/builds/37443748) (commit https://github.com/karma-runner/karma/commit/1665e403a3 by @johnjbarton)
karmarunnerbot: :white_check_mark: [Build karma 484 completed](https://ci.appveyor.com/project/karmarunnerbot/karma-8n04t/builds/37443752) (commit https://github.com/karma-runner/karma/commit/1665e403a3 by @johnjbarton)
AppVeyorBot: :white_check_mark: [Build karma 2884 completed](https://ci.appveyor.com/project/dignifiedquire/karma/builds/37444488) (commit https://github.com/karma-runner/karma/commit/97e61bbf34 by @johnjbarton)
karmarunnerbot: :white_check_mark: [Build karma 486 completed](https://ci.appveyor.com/project/karmarunnerbot/karma-8n04t/builds/37444489) (commit https://github.com/karma-runner/karma/commit/97e61bbf34 by @johnjbarton)
karmarunnerbot: :white_check_mark: [Build karma 494 completed](https://ci.appveyor.com/project/karmarunnerbot/karma/builds/37460309) (commit https://github.com/karma-runner/karma/commit/3f155edc5a by @johnjbarton)
AppVeyorBot: :white_check_mark: [Build karma 2891 completed](https://ci.appveyor.com/project/dignifiedquire/karma/builds/37460308) (commit https://github.com/karma-runner/karma/commit/3f155edc5a by @johnjbarton)
karmarunnerbot: :white_check_mark: [Build karma 493 completed](https://ci.appveyor.com/project/karmarunnerbot/karma-8n04t/builds/37460311) (commit https://github.com/karma-runner/karma/commit/3f155edc5a by @johnjbarton)

Patch:
diff --git a/lib/plugin.js b/lib/plugin.js
index 2e0ad5a2..20a18059 100644
--- a/lib/plugin.js
+++ b/lib/plugin.js
@@ -50,4 +50,40 @@ function resolve (plugins, emitter) {
   return modules
 }
 
-exports.resolve = resolve
+/**
+  Create a function to handle errors in plugin loading.
+  @param {Object} injector, the dict of dependency injection objects.
+  @return function closed over injector, which reports errors.
+*/
+function createInstantiatePlugin (injector) {
+  const emitter = injector.get('emitter')
+  // Cache to avoid report errors multiple times per plugin.
+  const pluginInstances = new Map()
+  return function instantiatePlugin (kind, name) {
+    if (pluginInstances.has(name)) {
+      return pluginInstances.get(name)
+    }
+
+    let p
+    try {
+      p = injector.get(`${kind}:${name}`)
+      if (!p) {
+        log.error(`Failed to instantiate ${kind} ${name}`)
+        emitter.emit('load_error', kind, name)
+      }
+    } catch (e) {
+      if (e.message.includes(`No provider for "${kind}:${name}"`)) {
+        log.error(`Cannot load "${name}", it is not registered!\n  Perhaps you are missing some plugin?`)
+      } else {
+        log.error(`Cannot load "${name}"!\n  ` + e.stack)
+      }
+      emitter.emit('load_error', kind, name)
+    }
+    pluginInstances.set(name, p, `${kind}:${name}`)
+    return p
+  }
+}
+
+createInstantiatePlugin.$inject = ['injector']
+
+module.exports = { resolve, createInstantiatePlugin }
diff --git a/lib/preprocessor.js b/lib/preprocessor.js
index 4b56a363..b6bf7569 100644
--- a/lib/preprocessor.js
+++ b/lib/preprocessor.js
@@ -70,36 +70,8 @@ async function runProcessors (preprocessors, file, content) {
   file.sha = CryptoUtils.sha1(content)
 }
 
-function createPriorityPreprocessor (config = {}, preprocessorPriority, basePath, injector) {
-  const emitter = injector.get('emitter')
-  const instances = new Map()
-
-  function instantiatePreprocessor (name) {
-    if (instances.has(name)) {
-      return instances.get(name)
-    }
-
-    let p
-    try {
-      p = injector.get('preprocessor:' + name)
-      if (!p) {
-        log.error(`Failed to instantiate preprocessor ${name}`)
-        emitter.emit('load_error', 'preprocessor', name)
-      }
-    } catch (e) {
-      if (e.message.includes(`No provider for "preprocessor:${name}"`)) {
-        log.error(`Can not load "${name}", it is not registered!\n  Perhaps you are missing some plugin?`)
-      } else {
-        log.error(`Can not load "${name}"!\n  ` + e.stack)
-      }
-      emitter.emit('load_error', 'preprocessor', name)
-    }
-
-    instances.set(name, p)
-    return p
-  }
-  _.union.apply(_, Object.values(config)).forEach(instantiatePreprocessor)
-
+function createPriorityPreprocessor (config = {}, preprocessorPriority, basePath, instantiatePlugin) {
+  _.union.apply(_, Object.values(config)).forEach((name) => instantiatePlugin('preprocessor', name))
   return async function preprocess (file) {
     const buffer = await tryToRead(file.originalPath, log)
     let isBinary = file.isBinary
@@ -121,7 +93,7 @@ function createPriorityPreprocessor (config = {}, preprocessorPriority, basePath
       .sort((a, b) => b[1] - a[1])
       .map((duo) => duo[0])
       .reduce((preProcs, name) => {
-        const p = instantiatePreprocessor(name)
+        const p = instantiatePlugin('preprocessor', name)
 
         if (!isBinary || (p && p.handleBinaryFiles)) {
           preProcs.push(p)
@@ -135,5 +107,5 @@ function createPriorityPreprocessor (config = {}, preprocessorPriority, basePath
   }
 }
 
-createPriorityPreprocessor.$inject = ['config.preprocessors', 'config.preprocessor_priority', 'config.basePath', 'injector']
+createPriorityPreprocessor.$inject = ['config.preprocessors', 'config.preprocessor_priority', 'config.basePath', 'instantiatePlugin']
 exports.createPriorityPreprocessor = createPriorityPreprocessor
diff --git a/lib/server.js b/lib/server.js
index 0cf94ce9..aa96b76e 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -76,6 +76,7 @@ class Server extends KarmaEventEmitter {
       watcher: ['value', watcher],
       launcher: ['factory', Launcher.factory],
       config: ['value', config],
+      instantiatePlugin: ['factory', plugin.createInstantiatePlugin],
       preprocess: ['factory', preprocessor.createPriorityPreprocessor],
       fileList: ['factory', FileList.factory],
       webServer: ['factory', createWebServer],


Test patch:
diff --git a/test/unit/mocha-globals.js b/test/unit/mocha-globals.js
index 8afdd587..6659714e 100644
--- a/test/unit/mocha-globals.js
+++ b/test/unit/mocha-globals.js
@@ -1,6 +1,7 @@
 const sinon = require('sinon')
 const chai = require('chai')
 const logger = require('../../lib/logger')
+const recording = require('log4js/lib/appenders/recording')
 
 // publish globals that all specs can use
 global.expect = chai.expect
@@ -15,13 +16,14 @@ chai.use(require('chai-subset'))
 beforeEach(() => {
   global.sinon = sinon.createSandbox()
 
-  // set logger to log INFO, but do not append to console
-  // so that we can assert logs by logger.on('info', ...)
-  logger.setup('INFO', false, [])
+  // Use https://log4js-node.github.io/log4js-node/recording.html to verify logs
+  const vcr = { vcr: { type: 'recording' } }
+  logger.setup('INFO', false, vcr)
 })
 
 afterEach(() => {
   global.sinon.restore()
+  recording.erase()
 })
 
 // TODO(vojta): move to helpers or something
diff --git a/test/unit/plugin.spec.js b/test/unit/plugin.spec.js
new file mode 100644
index 00000000..3ec9824d
--- /dev/null
+++ b/test/unit/plugin.spec.js
@@ -0,0 +1,61 @@
+'use strict'
+
+const createInstantiatePlugin = require('../../lib/plugin').createInstantiatePlugin
+
+describe('plugin', () => {
+  describe('createInstantiatePlugin', () => {
+    it('creates the instantiatePlugin function', () => {
+      const fakeGet = sinon.stub()
+      const fakeInjector = { get: fakeGet }
+
+      expect(typeof createInstantiatePlugin(fakeInjector)).to.be.equal('function')
+      expect(fakeGet).to.have.been.calledWith('emitter')
+    })
+
+    it('creates the instantiatePlugin function', () => {
+      const fakes = {
+        emitter: { emit: sinon.stub() }
+      }
+      const fakeInjector = { get: (id) => fakes[id] }
+
+      const instantiatePlugin = createInstantiatePlugin(fakeInjector)
+      expect(typeof instantiatePlugin('kind', 'name')).to.be.equal('undefined')
+      expect(fakes.emitter.emit).to.have.been.calledWith('load_error', 'kind', 'name')
+    })
+
+    it('caches plugins', () => {
+      const fakes = {
+        emitter: { emit: sinon.stub() },
+        'kind:name': { my: 'plugin' }
+      }
+      const fakeInjector = {
+        get: (id) => {
+          return fakes[id]
+        }
+      }
+
+      const instantiatePlugin = createInstantiatePlugin(fakeInjector)
+      expect(instantiatePlugin('kind', 'name')).to.be.equal(fakes['kind:name'])
+      fakeInjector.get = (id) => { throw new Error('failed to cache') }
+      expect(instantiatePlugin('kind', 'name')).to.be.equal(fakes['kind:name'])
+    })
+
+    it('errors if the injector errors', () => {
+      const fakes = {
+        emitter: { emit: sinon.stub() }
+      }
+      const fakeInjector = {
+        get: (id) => {
+          if (id in fakes) {
+            return fakes[id]
+          }
+          throw new Error('fail')
+        }
+      }
+
+      const instantiatePlugin = createInstantiatePlugin(fakeInjector)
+      expect(typeof instantiatePlugin('unknown', 'name')).to.be.equal('undefined')
+      expect(fakes.emitter.emit).to.have.been.calledWith('load_error', 'unknown', 'name')
+    })
+  })
+})
diff --git a/test/unit/preprocessor.spec.js b/test/unit/preprocessor.spec.js
index 2b5fefbc..5f0b7860 100644
--- a/test/unit/preprocessor.spec.js
+++ b/test/unit/preprocessor.spec.js
@@ -1,18 +1,19 @@
 'use strict'
 
 const mocks = require('mocks')
-const di = require('di')
 const path = require('path')
 
-const events = require('../../lib/events')
-
 describe('preprocessor', () => {
   let m
   let mockFs
-  let emitterSetting
   // mimic first few bytes of a pdf file
   const binarydata = Buffer.from([0x25, 0x50, 0x44, 0x66, 0x46, 0x00])
 
+  // Each test will define a spy; the fakeInstatiatePlugin will return it.
+  let fakePreprocessor
+
+  const simpleFakeInstantiatePlugin = () => { return fakePreprocessor }
+
   beforeEach(() => {
     mockFs = mocks.fs.create({
       some: {
@@ -32,22 +33,16 @@ describe('preprocessor', () => {
       'graceful-fs': mockFs,
       minimatch: require('minimatch')
     }
-    emitterSetting = { emitter: ['value', new events.EventEmitter()] }
     m = mocks.loadFile(path.join(__dirname, '/../../lib/preprocessor.js'), mocks_)
   })
 
   it('should preprocess matching file', async () => {
-    const fakePreprocessor = sinon.spy((content, file, done) => {
+    fakePreprocessor = sinon.spy((content, file, done) => {
       file.path = file.path + '-preprocessed'
       done(null, 'new-content')
     })
 
-    const injector = new di.Injector([{
-      'preprocessor:fake': [
-        'factory', function () { return fakePreprocessor }
-      ]
-    }, emitterSetting])
-    const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, simpleFakeInstantiatePlugin)
 
     const file = { originalPath: '/some/a.js', path: 'path' }
 
@@ -58,15 +53,12 @@ describe('preprocessor', () => {
   })
 
   it('should match directories starting with a dot', async () => {
-    const fakePreprocessor = sinon.spy((content, file, done) => {
+    fakePreprocessor = sinon.spy((content, file, done) => {
       file.path = file.path + '-preprocessed'
       done(null, 'new-content')
     })
 
-    const injector = new di.Injector([{
-      'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
-    }, emitterSetting])
-    const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, simpleFakeInstantiatePlugin)
 
     const file = { originalPath: '/some/.dir/a.js', path: 'path' }
 
@@ -77,15 +69,12 @@ describe('preprocessor', () => {
   })
 
   it('should get content if preprocessor is an async function or return Promise with content', async () => {
-    const fakePreprocessor = sinon.spy(async (content, file, done) => {
+    fakePreprocessor = sinon.spy(async (content, file, done) => {
       file.path = file.path + '-preprocessed'
       return 'new-content'
     })
 
-    const injector = new di.Injector([{
-      'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
-    }, emitterSetting])
-    const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, simpleFakeInstantiatePlugin)
 
     const file = { originalPath: '/some/.dir/a.js', path: 'path' }
 
@@ -96,15 +85,12 @@ describe('preprocessor', () => {
   })
 
   it('should get content if preprocessor is an async function still calling done()', async () => {
-    const fakePreprocessor = sinon.spy(async (content, file, done) => {
+    fakePreprocessor = sinon.spy(async (content, file, done) => {
       file.path = file.path + '-preprocessed'
       done(null, 'new-content')
     })
 
-    const injector = new di.Injector([{
-      'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
-    }, emitterSetting])
-    const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, simpleFakeInstantiatePlugin)
 
     const file = { originalPath: '/some/.dir/a.js', path: 'path' }
 
@@ -115,16 +101,13 @@ describe('preprocessor', () => {
   })
 
   it('should check patterns after creation when invoked', async () => {
-    const fakePreprocessor = sinon.spy((content, file, done) => {
+    fakePreprocessor = sinon.spy((content, file, done) => {
       file.path = file.path + '-preprocessed'
       done(null, 'new-content')
     })
 
-    const injector = new di.Injector([{
-      'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
-    }, emitterSetting])
     const config = { '**/*.txt': ['fake'] }
-    const pp = m.createPriorityPreprocessor(config, {}, null, injector)
+    const pp = m.createPriorityPreprocessor(config, {}, null, simpleFakeInstantiatePlugin)
 
     const file = { originalPath: '/some/a.js', path: 'path' }
 
@@ -137,14 +120,11 @@ describe('preprocessor', () => {
   })
 
   it('should ignore not matching file', async () => {
-    const fakePreprocessor = sinon.spy((content, file, done) => {
+    fakePreprocessor = sinon.spy((content, file, done) => {
       done(null, '')
     })
 
-    const injector = new di.Injector([{
-      'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
-    }, emitterSetting])
-    const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, simpleFakeInstantiatePlugin)
 
     const file = { originalPath: '/some/a.txt', path: 'path' }
 
@@ -153,34 +133,33 @@ describe('preprocessor', () => {
   })
 
   it('should apply all preprocessors', async () => {
-    const fakePreprocessor1 = sinon.spy((content, file, done) => {
-      file.path = file.path + '-p1'
-      done(null, content + '-c1')
-    })
-
-    const fakePreprocessor2 = sinon.spy((content, file, done) => {
-      file.path = file.path + '-p2'
-      done(content + '-c2')
-    })
-
-    const injector = new di.Injector([{
-      'preprocessor:fake1': ['factory', function () { return fakePreprocessor1 }],
-      'preprocessor:fake2': ['factory', function () { return fakePreprocessor2 }]
-    }, emitterSetting])
+    const fakes = {
+      fake1: sinon.spy((content, file, done) => {
+        file.path = file.path + '-p1'
+        done(null, content + '-c1')
+      }),
+      fake2: sinon.spy((content, file, done) => {
+        file.path = file.path + '-p2'
+        done(content + '-c2')
+      })
+    }
+    function fakeInstatiatePlugin (kind, name) {
+      return fakes[name]
+    }
 
-    const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake1', 'fake2'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake1', 'fake2'] }, {}, null, fakeInstatiatePlugin)
 
     const file = { originalPath: '/some/a.js', path: 'path' }
 
     await pp(file)
-    expect(fakePreprocessor1).to.have.been.calledOnce
-    expect(fakePreprocessor2).to.have.been.calledOnce
+    expect(fakes.fake1).to.have.been.calledOnce
+    expect(fakes.fake2).to.have.been.calledOnce
     expect(file.path).to.equal('path-p1-p2')
     expect(file.content).to.equal('content-c1-c2')
   })
 
   it('should compute SHA', async () => {
-    const pp = m.createPriorityPreprocessor({}, {}, null, new di.Injector([emitterSetting]))
+    const pp = m.createPriorityPreprocessor({}, {}, null, simpleFakeInstantiatePlugin)
     const file = { originalPath: '/some/a.js', path: 'path' }
 
     await pp(file)
@@ -198,15 +177,11 @@ describe('preprocessor', () => {
   })
 
   it('should compute SHA from content returned by a processor', async () => {
-    const fakePreprocessor = sinon.spy((content, file, done) => {
+    fakePreprocessor = sinon.spy((content, file, done) => {
       done(null, content + '-processed')
     })
 
-    const injector = new di.Injector([{
-      'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
-    }, emitterSetting])
-
-    const pp = m.createPriorityPreprocessor({ '**/a.js': ['fake'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/a.js': ['fake'] }, {}, null, simpleFakeInstantiatePlugin)
 
     const fileProcess = { originalPath: '/some/a.js', path: 'path' }
     const fileSkip = { originalPath: '/some/b.js', path: 'path' }
@@ -221,15 +196,11 @@ describe('preprocessor', () => {
   })
 
   it('should return error if any preprocessor fails', () => {
-    const failingPreprocessor = sinon.spy((content, file, done) => {
+    fakePreprocessor = sinon.spy((content, file, done) => {
       done(new Error('Some error'), null)
     })
 
-    const injector = new di.Injector([{
-      'preprocessor:failing': ['factory', function () { return failingPreprocessor }]
-    }, emitterSetting])
-
-    const pp = m.createPriorityPreprocessor({ '**/*.js': ['failing'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*.js': ['failing'] }, {}, null, simpleFakeInstantiatePlugin)
 
     const file = { originalPath: '/some/a.js', path: 'path' }
 
@@ -241,20 +212,20 @@ describe('preprocessor', () => {
   })
 
   it('should stop preprocessing after an error', async () => {
-    const failingPreprocessor = sinon.spy((content, file, done) => {
-      done(new Error('Some error'), null)
-    })
-
-    const fakePreprocessor = sinon.spy((content, file, done) => {
-      done(null, content)
-    })
+    const fakes = {
+      failing: sinon.spy((content, file, done) => {
+        done(new Error('Some error'), null)
+      }),
+      fake: sinon.spy((content, file, done) => {
+        done(null, content)
+      })
+    }
 
-    const injector = new di.Injector([{
-      'preprocessor:failing': ['factory', function () { return failingPreprocessor }],
-      'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
-    }, emitterSetting])
+    function fakeInstantiatePlugin (kind, name) {
+      return fakes[name]
+    }
 
-    const pp = m.createPriorityPreprocessor({ '**/*.js': ['failing', 'fake'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*.js': ['failing', 'fake'] }, {}, null, fakeInstantiatePlugin)
 
     const file = { originalPath: '/some/a.js', path: 'path' }
 
@@ -263,7 +234,7 @@ describe('preprocessor', () => {
     }, (err) => {
       expect(err.message).to.equal('Some error')
     })
-    expect(fakePreprocessor).not.to.have.been.called
+    expect(fakes.fake).not.to.have.been.called
   })
 
   describe('when fs.readFile fails', () => {
@@ -274,15 +245,11 @@ describe('preprocessor', () => {
     })
 
     it('should retry up to 3 times', async () => {
-      const fakePreprocessor = sinon.spy((content, file, done) => {
+      fakePreprocessor = sinon.spy((content, file, done) => {
         done(null, content)
       })
 
-      const injector = new di.Injector([{
-        'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
-      }, emitterSetting])
-
-      const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, injector)
+      const pp = m.createPriorityPreprocessor({ '**/*.js': ['fake'] }, {}, null, simpleFakeInstantiatePlugin)
 
       await pp(file).then(() => {
         throw new Error('Should be rejected')
@@ -295,9 +262,7 @@ describe('preprocessor', () => {
     })
 
     it('should throw after 3 retries', async () => {
-      const injector = new di.Injector([{}, emitterSetting])
-
-      const pp = m.createPriorityPreprocessor({ '**/*.js': [] }, {}, null, injector)
+      const pp = m.createPriorityPreprocessor({ '**/*.js': [] }, {}, null, simpleFakeInstantiatePlugin)
 
       await pp(file).then(() => {
         throw new Error('Should be rejected')
@@ -309,15 +274,11 @@ describe('preprocessor', () => {
   })
 
   it('should not preprocess binary files by default', async () => {
-    const fakePreprocessor = sinon.spy((content, file, done) => {
+    fakePreprocessor = sinon.spy((content, file, done) => {
       done(null, content)
     })
 
-    const injector = new di.Injector([{
-      'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
-    }, emitterSetting])
-
-    const pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, simpleFakeInstantiatePlugin)
 
     const file = { originalPath: '/some/photo.png', path: 'path' }
 
@@ -327,15 +288,11 @@ describe('preprocessor', () => {
   })
 
   it('should not preprocess files configured to be binary', async () => {
-    const fakePreprocessor = sinon.spy((content, file, done) => {
+    fakePreprocessor = sinon.spy((content, file, done) => {
       done(null, content)
     })
 
-    const injector = new di.Injector([{
-      'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
-    }, emitterSetting])
-
-    const pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, simpleFakeInstantiatePlugin)
 
     const file = { originalPath: '/some/proto.pb', path: 'path', isBinary: true }
 
@@ -345,15 +302,11 @@ describe('preprocessor', () => {
   })
 
   it('should preprocess files configured not to be binary', async () => {
-    const fakePreprocessor = sinon.spy((content, file, done) => {
+    fakePreprocessor = sinon.spy((content, file, done) => {
       done(null, content)
     })
 
-    const injector = new di.Injector([{
-      'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
-    }, emitterSetting])
-
-    const pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, simpleFakeInstantiatePlugin)
 
     // Explicit false for isBinary
     const file = { originalPath: '/some/proto.pb', path: 'path', isBinary: false }
@@ -364,16 +317,12 @@ describe('preprocessor', () => {
   })
 
   it('should preprocess binary files if handleBinaryFiles=true', async () => {
-    const fakePreprocessor = sinon.spy((content, file, done) => {
+    fakePreprocessor = sinon.spy((content, file, done) => {
       done(null, content)
     })
     fakePreprocessor.handleBinaryFiles = true
 
-    const injector = new di.Injector([{
-      'preprocessor:fake': ['factory', function () { return fakePreprocessor }]
-    }, emitterSetting])
-
-    const pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, simpleFakeInstantiatePlugin)
 
     const file = { originalPath: '/some/photo.png', path: 'path' }
 
@@ -383,15 +332,11 @@ describe('preprocessor', () => {
   })
 
   it('should not preprocess binary files with capital letters in extension', async () => {
-    const fakePreprocessor = sinon.spy((content, file, done) => {
+    fakePreprocessor = sinon.spy((content, file, done) => {
       done(null, content)
     })
 
-    const injector = new di.Injector([{
-      'preprocessor:fake': ['factory', function () { fakePreprocessor }]
-    }, emitterSetting])
-
-    const pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, injector)
+    const pp = m.createPriorityPreprocessor({ '**/*': ['fake'] }, {}, null, simpleFakeInstantiatePlugin)
 
     const file = { originalPath: '/some/CAM_PHOTO.JPG', path: 'path' }
 
@@ -402,72 +347,68 @@ describe('preprocessor', () => {
 
   it('should merge lists of preprocessors using default priority', async () => {
     const callOrder = []
-    const fakePreprocessorA = sinon.spy((content, file, done) => {
-      callOrder.push('a')
-      done(null, content)
-    })
-    const fakePreprocessorB = sinon.spy((content, file, done) => {
-      callOrder.push('b')
-      done(null, content)
-    })
-    const fakePreprocessorC = sinon.spy((content, file, done) => {
-      callOrder.push('c')
-      done(null, content)
-    })
-    const fakePreprocessorD = sinon.spy((content, file, done) => {
-      callOrder.push('d')
-      done(null, content)
-    })
-
-    const injector = new di.Injector([{
-      'preprocessor:fakeA': ['factory', function () { return fakePreprocessorA }],
-      'preprocessor:fakeB': ['factory', function () { return fakePreprocessorB }],
-      'preprocessor:fakeC': ['factory', function () { return fakePreprocessorC }],
-      'preprocessor:fakeD': ['factory', function () { return fakePreprocessorD }]
-    }, emitterSetting])
+    const fakes = {
+      fakeA: sinon.spy((content, file, done) => {
+        callOrder.push('a')
+        done(null, content)
+      }),
+      fakeB: sinon.spy((content, file, done) => {
+        callOrder.push('b')
+        done(null, content)
+      }),
+      fakeC: sinon.spy((content, file, done) => {
+        callOrder.push('c')
+        done(null, content)
+      }),
+      fakeD: sinon.spy((content, file, done) => {
+        callOrder.push('d')
+        done(null, content)
+      })
+    }
+    function fakeInstantiatePlugin (kind, name) {
+      return fakes[name]
+    }
 
     const pp = m.createPriorityPreprocessor({
       '/*/a.js': ['fakeA', 'fakeB'],
       '/some/*': ['fakeB', 'fakeC'],
       '/some/a.js': ['fakeD']
-    }, {}, null, injector)
+    }, {}, null, fakeInstantiatePlugin)
 
     const file = { originalPath: '/some/a.js', path: 'path' }
 
     await pp(file)
-    expect(fakePreprocessorA).to.have.been.called
-    expect(fakePreprocessorB).to.have.been.called
-    expect(fakePreprocessorC).to.have.been.called
-    expect(fakePreprocessorD).to.have.been.called
+    expect(fakes.fakeA).to.have.been.called
+    expect(fakes.fakeB).to.have.been.called
+    expect(fakes.fakeC).to.have.been.called
+    expect(fakes.fakeD).to.have.been.called
 
     expect(callOrder).to.eql(['a', 'b', 'c', 'd'])
   })
 
   it('should merge lists of preprocessors obeying priority', async () => {
     const callOrder = []
-    const fakePreprocessorA = sinon.spy((content, file, done) => {
-      callOrder.push('a')
-      done(null, content)
-    })
-    const fakePreprocessorB = sinon.spy((content, file, done) => {
-      callOrder.push('b')
-      done(null, content)
-    })
-    const fakePreprocessorC = sinon.spy((content, file, done) => {
-      callOrder.push('c')
-      done(null, content)
-    })
-    const fakePreprocessorD = sinon.spy((content, file, done) => {
-      callOrder.push('d')
-      done(null, content)
-    })
-
-    const injector = new di.Injector([{
-      'preprocessor:fakeA': ['factory', function () { return fakePreprocessorA }],
-      'preprocessor:fakeB': ['factory', function () { return fakePreprocessorB }],
-      'preprocessor:fakeC': ['factory', function () { return fakePreprocessorC }],
-      'preprocessor:fakeD': ['factory', function () { return fakePreprocessorD }]
-    }, emitterSetting])
+    const fakes = {
+      fakeA: sinon.spy((content, file, done) => {
+        callOrder.push('a')
+        done(null, content)
+      }),
+      fakeB: sinon.spy((content, file, done) => {
+        callOrder.push('b')
+        done(null, content)
+      }),
+      fakeC: sinon.spy((content, file, done) => {
+        callOrder.push('c')
+        done(null, content)
+      }),
+      fakeD: sinon.spy((content, file, done) => {
+        callOrder.push('d')
+        done(null, content)
+      })
+    }
+    function fakeInstantiatePlugin (kind, name) {
+      return fakes[name]
+    }
 
     const priority = { fakeA: -1, fakeB: 1, fakeD: 100 }
 
@@ -475,15 +416,15 @@ describe('preprocessor', () => {
       '/*/a.js': ['fakeA', 'fakeB'],
       '/some/*': ['fakeB', 'fakeC'],
       '/some/a.js': ['fakeD']
-    }, priority, null, injector)
+    }, priority, null, fakeInstantiatePlugin)
 
     const file = { originalPath: '/some/a.js', path: 'path' }
 
     await pp(file)
-    expect(fakePreprocessorA).to.have.been.called
-    expect(fakePreprocessorB).to.have.been.called
-    expect(fakePreprocessorC).to.have.been.called
-    expect(fakePreprocessorD).to.have.been.called
+    expect(fakes.fakeA).to.have.been.called
+    expect(fakes.fakeB).to.have.been.called
+    expect(fakes.fakeC).to.have.been.called
+    expect(fakes.fakeD).to.have.been.called
 
     expect(callOrder).to.eql(['d', 'b', 'c', 'a'])
   })