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

Sugggestions on how to mock a function in a Tape test. #548

Closed
cagross opened this issue Feb 23, 2021 · 7 comments
Closed

Sugggestions on how to mock a function in a Tape test. #548

cagross opened this issue Feb 23, 2021 · 7 comments

Comments

@cagross
Copy link
Contributor

cagross commented Feb 23, 2021

Hello.

I have a class method which has a secondary function defined inside of it (see code below). But the secondary function is designed to throw an exception when run in a Node environment. I'd like to run a Tape unit test to test this method. I don't need the secondary function at all in my test--it is irrelevant. But when I run my test (see test code below), as expected, an exception is thrown. With Tape, what are the suggested/typical ways to mock this secondary function, so I can still call this method in a unit test?

Is there a way to run my unit test and mock this secondary function (myFetchFunction) to a simple function that will not throw an error? In other words, during the unit test, set myFetchFunction() to some simple function, like console.log('Mocking function')

For example, I believe Jest has a way to do this, with their spyOn() method. I'm not complaining or demanding you have to be Jest :-) I'm just wondering how other Tape users have approached this.

Thanks in advance.

PS: I had a search through previous issues here, but nothing jumped out at me. Also, I didn't see anything in the README file about this. But after this exercise, if you want, I'd be happy to add a little blurb there, for future Tape users that might be wondering this.

edit: In hindsight, the function I want to mock in this case may be impossible to mock. Am I right there? If so, then I guess we can ignore the example I presented.


class.js

class myClass {
  myFunc() {
    let myVar
    myFetchFunc(var) {
      if (env === 'node') throw 'Cannot run in Node';
    }
    this.myVar = 555
    myFetchFunc(this.myVar)
  }
}
module.exports = { myClass }

test.js

const tape = require('tape')
const { myClass } = require('class')

tape('Tests description.', t => {
  const myObj= myClass()
  const actual = myObj.myVal
  const expected  = 555
  t.equal(actual, expected, 'Test description.')

  t.end()
})
@ljharb
Copy link
Collaborator

ljharb commented Feb 23, 2021

tape has no built-in mocking; you may want to use npmjs.com/sinon for this. You can also use npmjs.com/sinon-sandbox, as well as t.teardown(), to reset mocks after the test is done.

@cagross
Copy link
Contributor Author

cagross commented Feb 23, 2021

OK thanks very much for that. Do you think it would be helpful to add a blurb like this to the README? Not so much to inform people that Tape doesn't have built-in mocking--I think many will know/assume that. But moreso to point them in the right direction, to a solution that has worked well with Tape in the past (i.e. sinon). If so, let me know and I'd be happy to do that and submit a PR.

FYI I'm the same guy that submitted #541 and #542 😃

@ljharb
Copy link
Collaborator

ljharb commented Feb 23, 2021

We could, but in the entire ecosystem everyone really only uses one of two options: sinon, or jest builtin mocking - so I’m not sure how much clarity we need to provide.

@cagross
Copy link
Contributor Author

cagross commented Feb 23, 2021

OK then, sounds good. At least now there's a GitHub issue in the Tape repo on the subject, for people that are clueless about mocking (like I was 😄).

I'll close this :-)

@cagross cagross closed this as completed Feb 23, 2021
@Raynos
Copy link
Collaborator

Raynos commented Feb 23, 2021

One approach you can take is to make non-trivial dependencies explicit. this makes the public API surface larger but makes writing tests simpler.

class myClass {
  constructor (options = {}) {
    this.myFetch = options.myFetch || (var) => {
      if (env === 'node') throw 'Cannot run in Node';
    }
  }

  myFunc() {
    let myVar
    this.myVar = 555
    this.myFetch(this.myVar)
  }
}
module.exports = { myClass }
tape('Tests description.', t => {
  const myObj= myClass({ myFetch: () => {} })
  const actual = myObj.myVal
  const expected  = 555
  t.equal(actual, expected, 'Test description.')

  t.end()
})

Another example of this pattern is using this.logger = options.logger vs a global logger. Using a global logger is super convenient and using options.logger is tedious but makes the code friendlier for tests.

@ljharb
Copy link
Collaborator

ljharb commented Feb 23, 2021

That’s indeed how you’d do it if you wanted dependency injection to be part of your api.

@cagross
Copy link
Contributor Author

cagross commented Feb 24, 2021

OK thanks for that dependency injection example. I knew dependency injection was an option, but hadn't yet taken the time to understand what it was exactly. Your example made it pretty clear to me, which I appreciate a lot :-)

That’s indeed how you’d do it if you wanted dependency injection to be part of your api.

OK noted. Before moving forward with this, I'd definitely chat with my manager about it.

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