It would be nice to have the power of let syntax inside mocha specs.
There is a similar feature request for jasmine https://github.com/pivotal/jasmine/pull/101?source=cc
Please provide detailed use cases, (a) why is this needed, (b) how could it be implemented and optionally (but much preferred) (c) a pull request with code implementation
just glanced at that PR, looks like what you can already do with before/beforeEach and putting things on this
looking at that PR and the PR it references, that's a lot of givens to read everywhere.
// without givens
describe('first level tests', function() {
var foo, subject;
beforeEach(function(){
foo = "bar";
subject = { foo: foo };
});
it('should call nested lazy-evaluated variables', function() {
expect(subject.foo).toBe(foo);
expect(foo).toBe("bar");
});
describe('second level tests', function() {
beforeEach(function(){
foo = "rab";
});
it('should change the value of foo', function() {
expect(subject.foo).toBe(given.foo);
expect(foo).toBe("rab");
});
});
});
// let's given it up
describe('first level tests', function() {
given('foo', function() { return "bar"; });
given('subject', function() { return {"foo": given.foo}; });
it('should call nested lazy-evaluated variables', function() {
expect(given.subject.foo).toBe(given.foo);
expect(given.foo).toBe("bar");
});
describe('second level tests', function() {
given('foo', function() { return "rab"; });
it('should change the value of foo', function() {
expect(given.subject.foo).toBe(given.foo);
expect(given.foo).toBe("rab");
});
});
});
lazy evaluation is the biggest difference between using let and vars. "why hasn't the record count changed?... ah! because it's being lazy evaluated!" — this is every rspec programmer at some point. having lazily evaluated stuff in your tests is like leaving mines to step on, blow up and boom! there goes 15 minutes figuring out why your code doesn't make sense.
hmm yeah I'm not fond of that, and it wouldn't work well with callbacks really, you might as well just use beforeEach. Closing for now
Just wanted to chime in here that the lazy evaluation is important to allow you to do very complicated setup at a high level while still allowing smaller nested tests to control their environment, for example:
// Assumes let() defines the key with a lazy block resolution and get() resolves a key to a value lazily using said block.
describe("Person", function() {
// Provide sensible defaults
let("firstName", function() { return "John"; });
let("lastName", function() { return "Smith"; });
// Construct the object using other lets.
let("person", function() { return new Person({firstName: get("firstName"), lastName: get("lastName")}); });
describe("HasFirstAndLastName()", function() {
context("when firstName is null", function()
// Override only the values we are testing against without having to worry about the rest. This let() for "firstName" overrides any defined higher up.
let("firstName", function() { return null; });
it("should return false", function() {
expect(get("person").HasFirstAndLastName()).to.be.false
});
);
context("when firstName is a string with length", function()
// Override only the values we are testing against without having to worry about the rest. This let() for "firstName" overrides any defined higher up.
let("firstName", function() { return "Daniel"; });
it("should return true", function() {
expect(get("person").HasFirstAndLastName()).to.be.true
});
);
});
});
This is a very simple, contrived example, but when working with very complex tests it allows your individual tests to be straightforward and readable. You cannot always accomplish the "deeper spec overrides" using before() blocks which is why let() exists in RSpec. Personally, I don't use before() at all in RSpec anymore, everything I do is done with let() and subject(), I'd love to see the equivalents in mocha.
Would mocha include a working pull request for this functionality, or are you actively against it? It may be something I'd be willing to work on as long as it wasn't a non-starter.
@dpehrson I found very interesting blog post about lazy evaluation in mocha specs: http://takidashi.com/rspecs-let-like-behavior-in-mocha.html
describe 'Animal', ->
beforeEach -> @animal = new Animal @kind
context 'kind of cow', ->
before -> @kind = 'cow'
it 'voice should eq "moo"', ->
@animal.voice().should.eq 'moo'
context 'kind of cat', ->
before -> @kind = 'cat'
it 'voice should eq "meow"', ->
@animal.voice().should.eq 'meow'
context 'kind of fish', ->
before -> @kind = 'fish'
it 'voice cant be heared', ->
(=> @animal.voice()).should.throw 'fish voice cant be heared'
In short you have to combine instance variables this.something (or @something in coffeescript) with before hook.
Unfortunately this feature is not well documented. For example this trick will not work with beforeEach hook.
@visionmedia what's the difference between beforeEach and before in this particular case?
@lucassus That's a really clever hack, good to know there is a solution, although I'd still like to see proper let() and subject() syntax officially.
Thanks for replying.
before is executed once per "suite" (describe blocks), beforeEach is every test case
@lucassus That example works fine when you only have one nested level of derived values, but if you tried to make @kind depend on another variable set in an inner context there is no combination of before and beforeEach that will make it work.
@visionmedia, to pose @dpehrson's question again: are you opposed to having let or something like it in mocha? I'm investigating whether it's possible to build let outside of mocha using the API it provides. If it isn't, would you at least be amenable to a PR that adds the API required to allow a let that is not included in mocha?
I don't see any reason to have it in mocha
That's unfortunate, it prevents mocha from scaling to larger applications with more complex test suites.
@visionmedia that's fair. As to my second question, would you be opposed to (hopefully minimal) changes to mocha's API to allow someone to build it outside of mocha?
@dpehrson never hurts to break the app into smaller modules! we're just getting started and we're pushing 300+ private modules
@eventualbuddha to be honest I don't see what this feature really does, or why it needs to be coupled with a test runner at all, just looks like something you could do with ES proxies, or regular old methods
@visionmedia I believe I found a way to do this with mocha's current API, so this isn't really pressing. However, if you're curious, here's a cut down version of what I'm trying to do (explanatory comments in-line):
// pretend let() is a global function that creates global getters
// that memoize the return value of their functions (or a static value).
describe('TransactionController', function() {
let('$controller', function() {
$container.register('controller:transactionsShow', Ember.Controller.extend());
return TransactionController.create({
content: $transaction,
container: $container
});
});
beforeEach(function() {
// If let() were implemented using beforeEach, this would cause
// $transactionData to be evaluated before it could be configured
// in the #hasTip nested describe.
setupFakeAPIResponse('/transactions/abc', $transactionData)
});
let('$transaction', function() {
return transactionFactory.model(USD(), $transactionData);
});
let('$container', function() {
return new Ember.Container();
});
describe('#hasTip', function() {
let('$transactionData', function() {
// If we tried to use before and beforeEach instead of let()
// as in the Animal example in a previous comment, we'd only
// be able to do one level of these overrides, preventing
// nested contexts from overriding $tipAmount properly.
return {tip_money: USD($tipAmount)};
});
context('when there is no tip', function() {
let('$tipAmount', 0);
it('is false', function() {
expect($controller.get('hasTip')).to.be.false;
});
});
context('when there is a tip', function() {
lazy('$tipAmount', 1);
it('is true', function() {
expect($controller.get('hasTip')).to.be.true;
});
});
});
});
ah, yeah I see, definitely not something that should be in mocha, when it's effectively just:
var container = function(){
var memo;
return function(){
return memo || (memo = new Ember.Container());
}
}();
which is easy in user-land and has no downside. Or just using beforeEach with this.container = new stuff would be similar
In case anyone is interested, I created a separate lazy-bdd interface that is the same as bdd but has RSpec-style lazy and subject macros: https://www.npmjs.com/package/mocha-lazy-bdd
One more implementation which doesn't use this: https://github.com/stalniy/bdd-lazy-var
I've also created a minimal implementation of this behavior called givens
Most helpful comment
Just wanted to chime in here that the lazy evaluation is important to allow you to do very complicated setup at a high level while still allowing smaller nested tests to control their environment, for example:
This is a very simple, contrived example, but when working with very complex tests it allows your individual tests to be straightforward and readable. You cannot always accomplish the "deeper spec overrides" using
before()blocks which is whylet()exists in RSpec. Personally, I don't usebefore()at all in RSpec anymore, everything I do is done withlet()andsubject(), I'd love to see the equivalents in mocha.Would mocha include a working pull request for this functionality, or are you actively against it? It may be something I'd be willing to work on as long as it wasn't a non-starter.