Asked  7 Months ago    Answers:  5   Viewed   287 times

I'm having issues getting Chai's expect.to.throw to work in a test for my node.js app. The test keeps failing on the thrown error, but If I wrap the test case in try and catch and assert on the caught error, it works.

Does expect.to.throw not work like I think it should or something?

it('should throw an error if you try to get an undefined property', function (done) {
  var params = { a: 'test', b: 'test', c: 'test' };
  var model = new TestModel(MOCK_REQUEST, params);

  // neither of these work
  expect(model.get('z')).to.throw('Property does not exist in model schema.');
  expect(model.get('z')).to.throw(new Error('Property does not exist in model schema.'));

  // this works
  try { 
    model.get('z'); 
  }
  catch(err) {
    expect(err).to.eql(new Error('Property does not exist in model schema.'));
  }

  done();
});

The failure:

19 passing (25ms)
  1 failing

  1) Model Base should throw an error if you try to get an undefined property:
     Error: Property does not exist in model schema.

 Answers

62

You have to pass a function to expect. Like this:

expect(model.get.bind(model, 'z')).to.throw('Property does not exist in model schema.');
expect(model.get.bind(model, 'z')).to.throw(new Error('Property does not exist in model schema.'));

The way you are doing it, you are passing to expect the result of calling model.get('z'). But to test whether something is thrown, you have to pass a function to expect, which expect will call itself. The bind method used above creates a new function which when called will call model.get with this set to the value of model and the first argument set to 'z'.

A good explanation of bind can be found here.

Tuesday, June 1, 2021
 
McAn
answered 7 Months ago
74

I would delete the module from Node's module cache before running the 2nd test:

var resolved = require.resolve(__dirname + '/../../config');
delete require.cache[resolved];

So when requiring it again, Node will load from scratch. Note that the code above will only delete the config module from the cache. If you need to delete the modules loaded by the require calls inside your config module, then you'll have to do the same as above for each of them too.

By the way, if your tests are going to become asynchronous, the you need the done callback like you currently have. If your tests are meant to remain synchronous as they are now, the you could remove done from the argument list of the callbacks you give to it and omit calling it.

Saturday, August 7, 2021
 
Lance
answered 4 Months ago
89

Here's an example using spies. https://github.com/mochajs/mocha/wiki/Spies

var sinon = require('sinon');
var EventEmitter = require('events').EventEmitter;

describe('EventEmitter', function(){
  describe('#emit()', function(){
    it('should invoke the callback', function(){
      var spy = sinon.spy();
      var emitter = new EventEmitter;

      emitter.on('foo', spy);
      emitter.emit('foo');
      spy.called.should.equal.true;
    })

    it('should pass arguments to the callbacks', function(){
      var spy = sinon.spy();
      var emitter = new EventEmitter;

      emitter.on('foo', spy);
      emitter.emit('foo', 'bar', 'baz');
      sinon.assert.calledOnce(spy);
      sinon.assert.calledWith(spy, 'bar', 'baz');
    })
  })
})
Tuesday, September 7, 2021
 
DHranger
answered 3 Months ago
17

Ok, the problem is that you're using setInterval which is async and you're trying to assert that the value was changed in a synchronous way.

Here's a modified version of your test, using sinonjs to simulate that the time passed.

var assert = require('chai').assert;
var sinon = require('sinon');

var thing = { position: 0 }
var thingOnScreen = { style: { left: '' } };

function startMovingThing(){
    var position = setInterval(function() {
        moveThing(10);
    }, 100);
}

function moveThing(number){
    thing.position += number;
    thingOnScreen.style.left = thing.position + 'px';
}

describe('Thing', function() {

  beforeEach(function() {
    this.clock = sinon.useFakeTimers();
  });

  afterEach(function() {
    this.clock = sinon.restore();
  });

  it('should increase position', function(){
    startMovingThing();
    this.clock.tick(101);
    assert.equal(thing.position, 10);
  });
});

In summary, sinonjs is replacing the global functionality of setInterval and is executing the code without having to wait for the specified milliseconds.

Thursday, October 21, 2021
 
Dejell
answered 2 Months ago
99

expect().to.throw(Error) will only work for sync functions. If you want a similar feature using async functions take a look at chai-as-promised

import chaiAsPromised from 'chai-as-promised';
import chai from 'chai';

chai.use(chaiAsPromised)
var expect = chai.expect;

describe('fuzzing tokenization with 1000 invalid values', () => {
  it('should throw an error - invalid value', async () => {
    for (var i = 0; i <= 1000; i++) {
      var req = {
        body: {
          value: fuzzer.mutate.string('1000000000000000')
        },
        user: {
          displayName: user
        }
      };

      await expect(tokenizer.tokenize(req)).to.eventually.be.rejectedWith(Error);
    }
  });
});
Saturday, November 20, 2021
 
helgoboss
answered 3 Weeks ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share