Mocking required dependencies for testing


#1

Hello,

I’m trying to test correctly my package, and I’ve been playing lately with Jasmine. My first try, due to my experience/background, has been try to implement a poor-man’s dependency injection, supplying the dependencies of a class via it’s constructor. This has become a problem very soon, because it’s affecting the design (among some other problems harder to explain).

If I imagine how the perfect scenario would be like, I would think in something similar to being able to require dependencies in my modules, but get mocked (spy) objects.

Exists any documentation about advanced testing of the packages?. I’ve found some tutorials by GitHub, but they all seem to be pretty basic. Also, I’ve found some tutorials about mocking require, but I have more doubts than new ideas. Any package that it’s a great example?

Thanks a lot for your time :blush:


#2

What is the reason you want to do this? Why not just require the dependency?


#3

Sorry @olmokramer, maybe I haven’t explained myself very well. From the unit testing perspective, I usually would use test doubles when this double makes a work that costs time, or when it calls some external service.

In my case, I’m using a terminal and launching some commands, and this terminal is invoked in a class. It could be something similar to:

MyTerminalService = require './my-terminal-service'
class MyClass:
    constructor: ->
        @myTerminalService =  new MyTerminalService
    myFunction: ->
        @myTerminalService.onSomeTaskFinished (data) =>
            console.log data

As this ‘MyTerminalService’ is using the real system and executing real tests is very hard to test, without preparing a real environment. I would like to supply to MyClass a mock object and not the real one, so I can control what it returns.

Hope this makes it better. Thanks a lot!


#4

Ah I see. Though ugly, I think patching the require function is an easy way to achieve this:

# put this at the top of your specs file

fs = require 'fs'
path = require 'path'

oldRequire = require
global.require = (modulePath, args...) ->
  newModulePath = path.resolve __dirname, modulePath
  # redirect to specs directory if modulePath starts with ./
  # and if the module exists in the specs directory
  if modulePath.match /^\.\// and fs.existsSync newModulePath
    modulePath = newModulePath
  # require the module
  oldRequire modulePath, args...

You can now put the files you want a mock version of in the specs directory, and they will only be require'd when the specs are running. The paths in your package must start with ./ for this to work though.

Is this what you want?


#5

The reason you only really see mature dependency injection frameworks for statically-typed languages like C# and Java is because dynamically-typed languages, such as Ruby, Python and JavaScript, don’t need it. Taking your example from above:

MyTerminalService = require './my-terminal-service'

class MyClass:
    constructor: ->
        @myTerminalService =  new MyTerminalService
    myFunction: ->
        @myTerminalService.onSomeTaskFinished (data) =>
            console.log data

It’s really easy to modify this to allow you to inject dependencies without needing to monkey-patch require:

MyTerminalService = require './my-terminal-service'

class MyClass:
    constructor: (terminal = new MyTerminalService) ->
        @myTerminalService = terminal

    myFunction: ->
        @myTerminalService.onSomeTaskFinished (data) =>
            console.log data

So your production code would use new MyClass to instantiate an instance of MyClass, but your test code could use new MyClass(new MockTerminal) to instantiate it. Then you can just implement MockTerminal however you like, such as:

class MockTerminal
  onSomeTaskFinished: (fn) ->
    # Don't actually do anything meaningful here, we don't care. But return 5.
    5

But Jasmine natively supports test spies. So you don’t even have to go to this much trouble. You can write a spec like this:

it 'returns 5', ->
  # Notice I don't have to inject a dependency for this to work
  foo = new MyClass
  spyOn(foo.myTerminalService, 'onSomeTaskFinished').andReturn(5)

  expect(foo.myFunction()).toEqual(5)

One could still call this approach “dependency injection” in the broader sense, but I’ve found that most people think of something bigger and more involved when they think of dependency injection.


#6

Yep… That’s much more elegant, and indeed the correct approach to this problem.

Please disregard my answer :stuck_out_tongue:


#7

One drawback to the final approach is that the test can be very brittle because it, by nature, has to have intimate knowledge of the internals of the class being tested, also called Code Under Test or CUT. If the class gets rewritten, those internals can change and the test can break … sometimes in really hard to diagnose ways. So you should often prefer to test only on the public interface of classes. But, there is a hybrid approach between the dependency injection style and the spy style.

So we go back to the dependency-injected code:

MyTerminalService = require './my-terminal-service'

class MyClass:
  constructor: (terminal = new MyTerminalService) ->
    @myTerminalService = terminal

  myFunction: ->
    @myTerminalService.onSomeTaskFinished (data) =>
      console.log data

And then we write this for our test code:

it 'returns 5', ->
  foo = {}
  spyOn(foo, 'onSomeTaskFinished').andReturn(5)
  bar = new MyClass(foo)

  expect(bar.myFunction()).toEqual(5)

This is the best of both worlds. You don’t have to figure out crazy ways of instantiating complex spies and the original code under test doesn’t have to be modified very much.

If you want to learn more about making your life easy while testing, this is a great talk by Sandi Metz:


#8

Sorry and thanks a lot!

I haven’t been able to work on my project these days and I need to process all the information received and make some tests. I think that it has been very noticeable my background with statically typed languages. I was pretty confident that things could be made easier.

I think that I’m having some problems to test because:

  • My background
  • Jasmine examples are in Javascript and as newbie, sometimes it’s difficult to translate.
  • At least, concerning the examples that I’ve found, I see more info about testing if X has been called than about creating test doubles.

As my last requirement, I would like to discuss how would you go testing atom.config.get. According to Jasmine docs, I would go for something similar to:

atom = { config: {} }
spyOn(atom.config, "get").and.returnValue('some value');

How can I control the input parameter?. I would like to return different values depending on the input. How do you usually mock atom core functions as this one?

I’ve asked a few times here lately, and I’ve found real help and high technical level :blush: . Thanks guys. I hope this will become a new package soon.

PS: @olmokramer thanks a lot also!


#9

As I said, in this other topic, I’ve written a lot of tests for my packages:

You can always feel free to look at any of my packages to get ideas on how to test things. For your specific question:

you can take a look at my tabs-to-spaces package. It has some things to do with the configuration. The really great thing is that you don’t have to do anything:

Basically, the Atom spec framework mocks out the config for you by reloading the original config before each spec:


#10

Fantastic! A lot of information to experiment. Thanks a lot @leedohm again.