20

I'm trying to stub/mock/override a function call during testing which writes a log to a DB.

function logit(msg) {
  writeMessageToDb(msg);
}

function tryingToTestThisFunction(){
  var error = processSomething();
  if (error) {
    logit(error);
  }
}

I'd like logit() to simply print to the console during testing...and doing a "isTesting()" if/else block inside the logit() function is not an option.

Is this possible without including some additional mocking framework. I'm currently using JsTestDriver for unit testing and have not had a chance to evaluate any mocking frameworks. An ideal solution at the moment would be to handle this without another framework.

Mrchief
  • 75,126
  • 20
  • 142
  • 189
tjjava
  • 201
  • 1
  • 2
  • 3

6 Answers6

10

I use Jasmine and Sinon.js (using Coffeescript), here's how I stub out the confirm() method to, for example, just return true.

beforeEach ->
  @confirmStub = sinon.stub(window, 'confirm')
  @confirmStub.returns(true)

afterEach ->
  @confirmStub.restore()
sivabudh
  • 31,807
  • 63
  • 162
  • 228
  • 4
    This case is different as Window.confirm() isn't a global function. – Lance Kind Oct 19 '15 at 01:48
  • It essentially *is* a global function though. Try `confirm('test')` in console. `window` is itself the global object (and even exposes `window.window`--as well as globally defined user functions). In Node, `global` would be the desired target (e.g., to stub `setTimeout` not taking forever). Or if on Node 12+ without need of IE support or non-updated browser versions, use `globalThis`. – Brett Zamir Apr 23 '21 at 22:23
4

In javascript the latest definition is the prevalent.

so just redefine the logit method after the first definition.

function logit(msg) {
  console.log(msg);
}

example : http://www.jsfiddle.net/gaby/UeeQZ/

Gabriele Petrioli
  • 191,379
  • 34
  • 261
  • 317
3

I have been working on exactly the same problem. The developers gave me an HTML5 app to test, so of course I can't change their code for testing. I decided to use qunit and sinon, along with sinon-qunit.

For a newb to JavaScript unit testing like me, I was going nuts with the sinon documentation and various examples on the web, as most of it seems for an implied environment that isn't mentioned. The code below is a complete page, so I hope nothing is left for confusion.

The function that I have to call is caller() and I can't do anything about stubme() because it's in the developer's code. However, I can add sinonstub() in my test code. But how to get it to work with sinon? The sinon documentation really confused me for a while, but below is the simple solution. The stub4stubme object can be used to control the stub action, and also get the information about what's happening with the stub calls.

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
    <link rel="stylesheet" href="qunit-1.12.0.css" type="text/css" media="screen" />
</head>
<body>
    <div id="qunit"></div>
    <div id="qunit-fixture"></div>
    <script src="sinon-1.7.3.js"></script>
    <script src="qunit-1.12.0.js"></script>
    <script src="sinon-qunit-0.8.0.js"></script>
    <script>

        // Dev code in another file
        function stubme() {
            return "stubme";
        }

        function caller() {
            return "caller " + stubme();
        }
        // End of dev code

        var sinonstub = function () {
            return "u haz bin stubbed";
        };

        test("Stubbing global environments", function () {
            equal(caller(), "caller stubme");

            var stub4stubme = this.stub(window, "stubme", sinonstub);

            equal(caller(), "caller u haz bin stubbed");
            ok(stubme.called);
        });

    </script>
</body>
</html>
bmiller
  • 41
  • 2
  • You've almost got your finger on my confusion. Sinon.stub(window, "stubme", sinonstub); Why is "window" the top level object name? There is something going on here about global variable environment, which in your example is called window because it's running in a window. But the original poster could be in Node.js or,.... so is it still "window" then? – Lance Kind Oct 19 '15 at 01:52
3

Javascript is not only runtime linked, but is the last word wins linked as well. This means you can redeclare the method with the behavior you want in your test (since your test has the last word):

function yourTest(){
    oldImpl = logit;   // An even better approach is to do this in a setup.
    logit = function(msg){ Console.log.apply(console, s.call(arguments));};
    // do you assertions: assert.... yada.

    logit = oldImpl;  // Do this to keep your test isolated from the others you'll be executing in this test run. An even better approach is to do this in a teardown.
}
Lance Kind
  • 949
  • 12
  • 32
  • 1
    This is the only correct answer. The ones above will only bring confusion when you want to unit test the overridden function as it'll not have its original functionality. – iSpain17 Feb 06 '20 at 09:28
1

can you just override the method on the window object? In Chrome console this works

function test() {console.log('test')};
window.test();
hvgotcodes
  • 118,147
  • 33
  • 203
  • 236
1

just override the logit function, this can be called anytime later than logit is defined.

(function(){
  //keep handle to original logit method.
  var ol = logit;

  //shorter lookup path for slice
  var s = Array.prototype.slice;

  //logit override
  logit = function() {
    //if in testing
    if (typeof IsTesting == "function" && !!IsTesting()) {
      //log the arguments
      console.log.apply(console, s.call(arguments));
    } else {
      //otherwise, call the original function.
      ol.apply(this, s.call(arguments))
    }
  }
}());
Tracker1
  • 19,103
  • 12
  • 80
  • 106
  • NOTE: you could specify "msg" as a paramater and pass that directly, the s.call(arguments) with .apply on the method will pass everything through, which works better for method overrides like this. – Tracker1 Dec 16 '11 at 16:48