Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stub one function and use originals for rest #368

Closed
narayanmp opened this issue May 11, 2018 · 6 comments
Closed

Stub one function and use originals for rest #368

narayanmp opened this issue May 11, 2018 · 6 comments

Comments

@narayanmp
Copy link

Description

I have a js file that has multiple functions. I want to stub out just one function in order to test another function that has a dependency on the first function.

Issue

When I do td.replace('path-to-js-file'), it replaces all the functions with a fake one. Probably as designed. Want to know if there is a way to stub one function and call another function(which is in the same file) that behaves as the original implementation.

@searls
Copy link
Member

searls commented May 12, 2018

This is by design, because what you're describing is a partial mock, and more specifically, a contaminated test subject, described in the linked wiki as:

There is a special sub-type of a partial mock, a which is when the subject itself is partially mocked in a test. This exacerbates all of the aforementioned issues. I call this a contaminated test subject, because the thing being evaluated by the test is also tainted by it, which might be expedient (for instance, specifying a depended-on function as part of the same code listing as the subject), but it will inevitably confuse the story the test is trying to tell to future readers.

One of the primary values of isolation testing is soliciting feedback on the design of the private API you're writing and, along with general usability, one things isolation tests can tell us is how to better organize our subject code in a way that's expressive and discoverable to others, and to insist on faking part of the thing being tested would be to ignore the feedback about fully and clearly separating the concerns being specified.

That said, you can accomplish what you are asking with:

const pathToJsFile = require('./path-to-js-file')
td.replace(pathToJsFile, 'fakeOne')

@searls searls closed this as completed May 12, 2018
@froxCZ
Copy link

froxCZ commented Aug 21, 2018

@searls I don't think your example works - at least not for me. Although I am aware partial mocks are bad practise, legacy codebase requires that. Unfortunately, I cannot find a workaround around that :/

@searls
Copy link
Member

searls commented Aug 21, 2018

@froxCZ that's ok. Can you give me a reproducible example showing why the above isn't working in your case?

@froxCZ
Copy link

froxCZ commented Aug 21, 2018

@searls Thanks a lot for your quick reply. Here it is.

// file.js
export function a() {
  return 'a'
}

export function b() {
  return a() + 'b'
}
// file.spec.js
/* eslint-env mocha */

import { expect } from 'chai'
import td from 'testdouble'

it('test', function() {
  const file = require('./file')
  const mockA = td.replace(file, 'a')
  td.when(mockA()).thenReturn('x')
  console.log(file.b()) // Still prints 'ab'
})

@searls
Copy link
Member

searls commented Aug 21, 2018

The issue you're having is made a little less obvious because you're (apparently) transpiling, so here's your example rewritten in something that doesn't require a build step:

//file.js
function a() {
  return 'a'
}

function b() {
  return a() + 'b'
}

module.exports.a = a
module.exports.b = b
//test.js
const td = require('testdouble')

const file = require('./file')
const mockA = td.replace(file, 'a')
td.when(mockA()).thenReturn('x')
console.log(file.b())

And in that case, as you said, running node test.js will indeed print ab.

The reason that this is happening is because the reference to a that is invoked by b is resolved in a scope that's local to the first module, and not in any way rewrite-able after the fact, unfortunately. To have any hope of changing that reference, whether or not you're using a mocking library to help you—right now you couldn't even redefine the method and change that call—you need to change how the b function references a by dangling it off some other object that is mutable.

So, for instance, if you change the file.js listing to:

let stuff
module.exports = stuff = {
  a: function a () {
    return 'a'
  },
  b: function b() {
    return stuff.a() + 'b'
  }
}

The test will print xb. Changing the reference to stuff.a in b makes the replaced reference to a reachable by the subject being contaminated/partially mocked. The fact this is so fraught and error prone is, I guess, another good reason to avoid contaminated test subjects since it's a pretty precarious configuration.

@froxCZ
Copy link

froxCZ commented Aug 21, 2018

Yea I was expecting something like this. Thanks a lot for clarification!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants