jupiter / simple-mock

Super simple stubs and spies with 1-step sandbox restore
MIT License
87 stars 12 forks source link

Restore a specific mock/spy #9

Closed thislooksfun closed 8 years ago

thislooksfun commented 8 years ago

Hello! First off, I love this tool. I took one look at sinon and instantly sought a more intuitive method, and I wound up finding this! I only have one request, which is a restore() function on individual mocks. In my tests I'm finding myself needing to mocks for one method call that need to be undone before the next one, and I would like a way to not have to redo all the other mocks and spies.

Proposed format:

simple = require("simple-mock")

simple.mock(console, "log")
mockDir = simple.mock(object, "key", "new information")
do_something()
mockDir.restore()
// console.log is still being spied on
jupiter commented 8 years ago

Hey, sorry I missed this issue.

In this particular example, the mockDir.restore() API would not be possible due to mockDir being a string. There are some tricks we can pull, but I prefer to keep it as clean as possible. The only other option is to not return the mocked value, but to return the reference to the mock. This would be a problem for chaining on function mocks, so we'd have to return something different for value mocks vs function mocks. This would work, but it could be a breaking change for some, which could be painful, however rare this may be. Do you have any ideas on how you'd want to implement it?

I usually try to keep shared setup for subsequent tests in something such as a beforeEach block or just a setup function.

thislooksfun commented 8 years ago

So, correct me if I'm wrong: the problem is that currently the simple.mock function returns the object that is being replaced, and you (understandably) don't want to change that because it would break existing setups?

jupiter commented 8 years ago

Correct. This behaviour enables you to chain behaviour instructions, e.g.

simple.mock(object, 'key')
.returnWith('something')
.throwWith(new Error('already returned')`

I do realise that the case for simple value substitution, i.e. not a method mock, does not benefit from chaining anyway. Would an API such as simple.restore(object, 'key') be good?

thislooksfun commented 8 years ago

That method could work, but I'm worried about multiple mocks replacing the same value. I've been playing around a bit, but so far I have been unable to come up with a good way to keep the string and still add a restore function, but I believe the chaining problem can be easily solved by adding the restore function to the mock. It doesn't have to be chainable itself, because why would you want to call something else after it, but it can go somewhere in here

My work so far:

var str = "hi";

var strWithRestore = Object(str);

strWithRestore.restoreMock = function() {
  return console.log("Hello!");
};

var testing = {
  hi: "1",
  hello: "2"
};

testing[str];  // Gives '1'
testing[strWithRestore];  // Still gives '1'
typeof strWithRestore == "string"  // Doesn't work, returns 'object'
jupiter commented 8 years ago

That's perhaps a bit too clever, changing to a string object.

I've implemented the simple.restore(object, 'key') method in the above commit. This should not break any existing APIs and is even safe if you mock a value multiple times - it will only restore the last mocked value.

thislooksfun commented 8 years ago

Awesome, thanks! My confusion about conflicting mocks was that I, for whatever silly reason, thought you meant to implement it like so:

mockDir = simple.mock(object, "key", "new information")
do_something()
simple.restore(mockDir)

Which would fail if mockDir was the same string as any other mock. I now understand what you actually meant by that, and I have to say, that's a very nice solution.