cleishm / jsmockito

Javascript mocking framework inspired by the awesome mockito

Home Page:http://jsmockito.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

mock doesn't mock but creates a spy instead

EleotleCram opened this issue · comments

Creating a mock with JsMockito doesn't create a mock, but creates a spy instead.

Consider:

with(JsMockito) {

  var foo = {
    bar: function() {
      console.log("This is the real implementation of bar!");
    }
  }

  console.log("foo.bar(): ");
  foo.bar();

  console.log("mockFoo.bar(): ");
  var mockFoo = mock(foo);
  mockFoo.bar();

  console.log("spyFoo.bar(): ");
  var spyFoo = spy(foo);
  spyFoo.bar();  
}

(jsbin source: http://jsbin.com/ayixov/4/edit)

The console output is (rather unexpectedly):

foo.bar(): 
This is the real implementation of bar!
mockFoo.bar(): 
This is the real implementation of bar!
spyFoo.bar(): 
This is the real implementation of bar!

(Please note that spy() and mock() both yield the same behaviour.)

Here is an equivalent program written in Java+Mockito:

package com.mycompany.app;

import static org.mockito.Mockito.*;

public class App {

    static class Foo {
        public void bar() {
            System.out.println("This is the real implementation of bar!");
        }
    }

    public static void main(String[] args) {
        System.out.println("foo.bar(): ");
        Foo foo = new Foo();
        foo.bar();

        System.out.println("mockFoo.bar(): ");
        Foo mockFoo = mock(foo.getClass());
        mockFoo.bar();

        System.out.println("spyFoo.bar(): ");
        Foo spyFoo = spy(foo);
        spyFoo.bar();
    }
}

(source code: http://www.megaupload.com/?d=DHWNHL5X)

Which yields the desired behaviour:

foo.bar(): 
This is the real implementation of bar!
mockFoo.bar(): 
spyFoo.bar(): 
This is the real implementation of bar!

(Please note the missing line "This is the real implementation of bar!" after `mockFoo.bar()' is invoked. This is to be expected, since mockFoo is expected to have mock implementations, i.e. stubs.)

Just to let you know, I'm looking into the matter myself as well...

Hi,

The problem is in your usage: you are passing a real object to 'mock', and not an object constructor. The mock method in JsMockito will then behave identically to a spy.

Try instead:

with(JsMockito) {
  var Foo = function() {
    this.bar = function() {
      console.log("This is the real implementation of bar!");
    }
  }

  var foo = new Foo();

  console.log("foo.bar(): ");
  foo.bar();

  console.log("mockFoo.bar(): ");
  var mockFoo = mock(Foo);
  mockFoo.bar();

  console.log("spyFoo.bar(): ");
  var spyFoo = spy(foo);
  spyFoo.bar();  
}

Too bad you closed the issue, because I kind of disagree with the approach you have taken with JsMockito.

We try to avoid constructor functions in our JavaScript code; we use true prototypal inheritance instead. Your suggestion only applies to JavaScript code that uses constructor functions.

I have two working approaches in fixing it in a less restrictive way. This way people are free to choose in whichever way they want you use JavaScript to create their objects and still be able to use JsMockito to mock their objects.

If you are not interested in this solution, it is okay with me, I'll just keep it in my personal branch.

I'm happy to discuss other approaches. It's just that in this case you have passed a fully formed object to 'mock' and it is behaving exactly as designed.

We can discuss any approach in solving the matter in code, however, the way JsMockito's mock and spy are currently designed to behave is, as I've stated previously, somewhat limiting in the ways people are allowed to use JavaScript if they want to use JsMockito as a mocking aid. So we will first have to agree upon a different design (fear not, I think a slight change in the current design will suffice).

My point of view regarding this change in design:

JavaScript allows several approaches in creating and manipulating objects. The new design needs to take this into account.

Let's look at the current design and where it derives from. Then we can see in which ways it applies (or does not apply) well to JavaScript. Let's first consider two common concepts in the context of source code testing, spy and mock, and see how they are typically used. A spy is typically used on an instance of an object, a mock is used on something not an instance, typically a class. In Mockito, written in Java, a statically typed language, this becomes clear because you are forced to either do:

Mockito.spy(new Foo())

or:

Mockito.mock(Foo.class)

Since JavaScript is a dynamically typed language, I understand that you opted to look at the argument types to distinguish between the two cases. This design works for many (if not all) classically typed languages. Unfortunately, in JavaScript, fundamentally a class-free object oriented language, this design will be too limited. This is because JavaScript uses prototypal inheritance instead (even though it allows you to emulate classical inheritance[1]). In prototypal inheritance, any object can be used as the prototype of any other object. This means that all objects are essentially instances.

Now it hopefully becomes clear that you cannot derrive what the author (programmer) intends to do by means of looking at the function arguments of either spy or mock. This will only work in JavaScript if the programmer uses some form of classical inheritance emulation.

Fortunately, spy and mock also yield semantics by themselves: If the programmer uses the word mock, in a classically typed language, he or she would have written: mock(Foo.class), and most likely would have meant for it to use stubs for all method implementations. If he/she, on the other hand, had meant for it to have the original methods in place, he/she would have used the word spy in combination with an object instance.

Hence, I suggest, that the new design derives the intended meaning of the programmer by distinguishing based on the use of the words mock and spy, rather than looking at the type of arguments passed to those functions. Then using mock will imply that the object that is passed will not receive the method calls, and, subsequently, using spy will imply that it will receive the method calls.

[1] http://www.crockford.com/javascript/inheritance.html

One simple approach that has minimum impact on code, though, probably not the most beautiful or advisable approach would be:

EleotleCram@12997e9

I'm aware of the different approaches to inheritance in Javascript (obviously). I simply hadn't considered it as necessary to support creating mocks directly from prototype objects - preferring instead to use constructor functions, which gives a model more similar to Mockito.

What you're making is a feature request. As such, I'm happy to provide support for it.

I've taken a slightly different approach from your patch, but essentially the result is the same. It's now been pushed.

Sorry for being elaborate. I can get carried away while trying to make my point sometimes. Thanks for your understanding.

I agree that your solution is preferred. It looks quite similar to the second approach I had in mind. I hadn't pursued it further after you initially closed the issue (since it involved quite a few more changes and for a while I thought it wasn't going to make it upstream anyway).

All in all, thanks for the fast response and also for your support!