Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stub https.request response.pipe with sinon.js?

Let's say, that I have this simple code:

var https = require('https');
var options = {
  host: 'openshift.redhat.com',
  port: 443,
  path: '/broker/rest/api',
  method: 'GET'
};
var req = https.request(options, function(response) {
  console.log(response.statusCode);
  response.pipe(save stream to file with fs)
});
req.on('error', function(e) {
  console.error(e);
});
req.end();

Well, I'm bit new with sinon.js and I'd like to ask: How to stub response.pipe()? Of course, I can make stub for https.request and return somethin with .on and .end and thats easy, but I have no idea, how to test if response.pipe() was called with proper arguments... (nodejs documentation says that response is callback) Documentation is not helpful in this case! ofc testing env is mocha, and can use chai too Please give me some advices or examples. Thanks, Matt

like image 539
Arogancky Avatar asked Feb 22 '17 18:02

Arogancky


People also ask

How do you stub a method using Sinon?

To stub a dependency (imported module) of a module under test you have to import it explicitly in your test and stub the desired method. For the stubbing to work, the stubbed method cannot be destructured, neither in the module under test nor in the test.

How does Sinon stub work?

The sinon. stub() substitutes the real function and returns a stub object that you can configure using methods like callsFake() . Stubs also have a callCount property that tells you how many times the stub was called. For example, the below code stubs out axios.

What is callsFake in Sinon stub?

In Sinon, a fake is a Function that records arguments, return value, the value of this and exception thrown (if any) for all of its calls. A fake is immutable: once created, the behavior will not change. Unlike sinon. spy and sinon. stub methods, the sinon.

What is stubbing a request?

What is a Stub? In testing land, a stub replaces real behavior with a fixed version. In the case of HTTP requests, instead of making the actual call, a stub fakes the call and provides a canned response that can be used to test against.


2 Answers

I wrapped your code into a function that accepts a callback because in current implementation we don't actually know when the piping is actually finished. So assuming we have a function like this:

const downloadToFile = function (options, callback) {
	let req = https.request(options, function (err, stream) {
		let writeStream = fs.createWriteStream('./output.json');
		stream.pipe(writeStream);

		//Notify that the content was successfully writtent into a file
		stream.on('end', () => callback(null));
		//Notify the caller that error happened.
		stream.on('error', err => callback(err));
	});

	req.end();
};

There are 3 problems to solve:

  1. As response is a readable stream. We want to mock the data it emits.
  2. We want to mock .pipe method check if we are piping to the right stream.
  3. We also need to mock https.request method not to make actual call

Here is how we can achieve this:

const {PassThrough} = require('stream');

describe('#downloadToFile', () => {
	it('should save the data to output.json', function (callback) {
		const mockResponse = `{"data": 123}`;
		//Using a built-in PassThrough stream to emit needed data.
		const mockStream = new PassThrough();
		mockStream.push(mockResponse);
		mockStream.end(); //Mark that we pushed all the data.

		//Patch the 'https' module not to make an actual call
		//but to return our stream instead
		sinon.stub(https, 'request', function (options, callback) {
			callback(null, mockStream);

			return {end: sinon.stub()}; //Stub end method btw
		});

		//Finally keep track of how 'pipe' is going to be called
		sinon.spy(mockStream, 'pipe');

		downloadToFile({url: 'http://google.com'}, (err) => {
			//Here you have the full control over what's happened
			sinon.assert.calledOnce(mockStream.pipe);
			//We can get the stream that we piped to.
			let writable = mockStream.pipe.getCall(0).args[0];
			assert.equal(writable.path, './output.json');

			//Tell mocha that the test is finished. Pass an error if any.
			callback(err);
		});
	});
});

Later you could make separate functions like: createMockedStream. Or even extract all these preparations into a separate method and keep only asserts in a test.

like image 104
Antonio Narkevich Avatar answered Sep 19 '22 05:09

Antonio Narkevich


From Sinon documentation, this has been removed from v3.0.0:

var stub = sinon.stub(object, "method", func);`

Instead you should use:

stub(obj, 'meth').callsFake(fn)
like image 21
Jim Peterson Avatar answered Sep 21 '22 05:09

Jim Peterson