2013-06-09 21 views
8

Ich verwende Mocha mit Sinon Unit-Test meine node.js Module. Ich habe andere Abhängigkeiten erfolgreich verspottet (andere Module, die ich geschrieben habe), aber ich habe Probleme beim Stoppen nicht reiner Funktionen (wie Math.random() und Date.now()). Ich habe Folgendes versucht (vereinfacht, so dass diese Frage nicht so lokalisiert ist), aber Math.random() wurde nicht wegen eines offensichtlichen Umfangs Problems gestempelt. Die Instanzen von Math sind unabhängig zwischen der Testdatei und mymodule.js.Stubbing Date.now() und Math.random()

test.js

var sinon = require('sinon'), 
    mymodule = require('./mymodule.js'), 
    other = require('./other.js'); 

describe('MyModule', function() { 
    describe('funcThatDependsOnRandom', function() { 
     it('should call other.otherFunc with a random num when no num provided', function() { 
      sinon.mock(other).expects('otherFunc').withArgs(0.5).once(); 
      sinon.stub(Math, 'random').returns(0.5); 

      funcThatDependsOnRandom(); // called with no args, so should call 
             // other.otherFunc with random num 

      other.verify(); // ensure expectation has been met 
     }); 
    }); 
}); 

Also in diesem erfundenen Beispiel würde functThatDependsOnRandom() wie folgt aussehen:

mymodule.js

var other = require('./other.js'); 

function funcThatDependsOnRandom(num) { 
    if(typeof num === 'undefined') num = Math.random(); 

    return other.otherFunc(num); 
} 

Ist es möglich, Math.random() Stub in Dieses Szenario mit Sinon?

Antwort

7

ja, das ist eine alte Frage, aber es ist gültig. Hier ist eine Antwort, die funktioniert, obwohl ich gerne Vorschläge hören würde, wie man es besser machen kann.

Der Weg, den ich damit im Browser behandelt habe, ist ein Proxy-Objekt zu erstellen. Beispielsweise können Sie das Fensterobjekt im Browser nicht stubben, sodass Sie ein Proxyobjekt namens windowProxy erstellen können. Wenn Sie den Speicherort abrufen möchten, erstellen Sie eine Methode in windowProxy namens location, die windowLocation zurückgibt oder setzt. Dann, wenn Sie testen, spielen Sie windowProxy.location.

Sie können das gleiche mit Node.js tun, aber es funktioniert nicht ganz so einfach. Die einfache Version besteht darin, dass ein Modul nicht mit dem privaten Namespace eines anderen Moduls verwechselt werden kann.

Die Lösung besteht darin, das Modul mockery zu verwenden. Wenn Sie nach der Initiierung des Spottes require() mit einem Parameter aufrufen, der dem entspricht, was Sie Spott zu Spott gesagt haben, können Sie die require-Anweisung überschreiben und Ihre eigenen Eigenschaften zurückgeben.

UPDATE: Ich habe ein voll funktionsfähiges Codebeispiel erstellt. Es ist auf Github at newz2000/dice-tdd und available via npm./END UPDATE

Die docs sind ziemlich gut, so dass ich sie schlagen vor, zu lesen, aber hier ist ein Beispiel:

Erstellen Sie eine Datei randomHelper.js mit Inhalt wie folgt aus:

module.exports.random = function() { 
    return Math.random(); 
} 

dann in Ihrem Code das benötigt eine Zufallszahl, du:

var randomHelper = require('./randomHelper'); 

console.log('A random number: ' + randomHelper.random()); 

Alles sollte wie normal funktionieren. Ihr Proxy-Objekt verhält sich genauso wie Math.random.

Es ist wichtig zu beachten, dass die require-Anweisung einen einzelnen Parameter akzeptiert, './randomHelper'. Das müssen wir beachten.

Jetzt in Ihrem Test, (Ich bin mit Mokka und chai zum Beispiel):

var sinon = require('sinon'); 
var mockery = require('mockery') 
var yourModule; // note that we didn't require() your module, we just declare it here 

describe('Testing my module', function() { 

    var randomStub; // just declaring this for now 

    before(function() { 
    mockery.enable({ 
     warnOnReplace: false, 
     warnOnUnregistered: false 
    }); 

    randomStub = sinon.stub().returns(0.99999); 

    mockery.registerMock('./randomHelper', randomStub) 
    // note that I used the same parameter that I sent in to requirein the module 
    // it is important that these match precisely 

    yourmodule = require('../yourmodule'); 
    // note that we're requiring your module here, after mockery is setup 
    } 

    after(function() { 
    mockery.disable(); 
    } 

    it('Should use a random number', function() { 
    callCount = randomStub.callCount; 

    yourmodule.whatever(); // this is the code that will use Math.random() 

    expect(randomStub.callCount).to.equal(callCount + 1); 
    } 
} 

Und das ist es. In diesem Fall gibt unser Stub immer 0.0.99999 zurück; Sie können es natürlich ändern.

+0

Ausgezeichnete Antwort. Sie können Proxy-Quotes anstelle von Spott verwenden. – Wtower

0

Sind Sie sicher, dass nicht spotten Math ist das Problem. Es scheint, dass diese Linie nicht viel Sinn machen:

sinon.mock(other).expects('otherFunc').withArgs(0.5).once(); 

Sie others in einem Modul verspotten, aber es in ein anderes verwenden. Ich denke nicht, dass Sie die verspottete Version in mymodule.js bekommen werden. Auf der anderen Seite sollte stubbing Math.random funktionieren, da dies für alle Module global ist.

Werfen Sie einen Blick auf diese SO für Mocking Abhängigkeiten in NodeJS-Tests.

+0

Korrigieren Sie mich, wenn ich falsch liege, aber ich dachte, dass Module zwischengespeichert wurden, nachdem ihre ersten erfordern. Daher sollte 'require ('./ other.js') in der Testsuite und im zu testenden Code dieselbe Instanz sein. Mit diesem Denken ging ich davon aus, dass (wie 'Math.random')" Anderes "in einem verspottet wird, um das Objekt im anderen zu modifizieren. Aber das funktioniert wahrscheinlich nicht, weil es anderen ein neues Objekt zuweist, anstatt Eigenschaften zu ersetzen. Irgendeine Weise, die du mit sinon weißt, davon zu umgehen? –

1

Versuchen:

sinon.stub(Math, "random", function(){ 
    return 0.5; 
    }); 
+2

Das funktioniert nicht, wenn sich die Funktion, die Math.random() verwendet, in einem anderen Modul befindet. Es funktioniert, wenn Sie REPL verwenden oder die Funktion in derselben .js-Datei ist. – newz2000