73

I've run into this issue in real code, but I put together a trivial example to prove the point.

The below code works fine. I've set up a variable in my root describe() block that is accessible within my sub-describe()s' it() blocks.

describe('simple object', function () {
    var orchard;

    beforeEach(function () {
        orchard = {
            trees: {
                apple: 10,
                orange : 20
            },
            bushes: {
                boysenberry : 40,
                blueberry: 35
            }
        };
    });

    describe('trees', function () {
        it ('should have apples and oranges', function() {
            var trees = orchard.trees;

            expect (trees.apple).toBeDefined();
            expect (trees.orange).toBeDefined();

            expect (trees.apple).toEqual(10);
            expect (trees.orange).toEqual(20);
        });
        it ('should NOT have pears or cherries', function() {
            var trees = orchard.trees;

            expect (trees.pear).toBeUndefined();
            expect (trees.cherry).toBeUndefined();
        });
    });
});

http://jsfiddle.net/w5bzrkh9/

However, if I try to DRY up my code a little by doing the following, it breaks:

describe('simple object', function () {
    var orchard;

    beforeEach(function () {
        orchard = {
            trees: {
                apple: 10,
                orange : 20
            },
            bushes: {
                boysenberry : 40,
                blueberry: 35
            }
        };
    });

    describe('trees', function () {
        var trees = orchard.trees; // TypeError: Cannot read property 'trees' of undefined

        it ('should have apples and oranges', function() {
            expect (trees.apple).toBeDefined();
            expect (trees.orange).toBeDefined();

            expect (trees.apple).toEqual(10);
            expect (trees.orange).toEqual(20);
        });
        it ('should NOT have pears or cherries', function() {
            expect (trees.pear).toBeUndefined();
            expect (trees.cherry).toBeUndefined();
        });
    });
});

http://jsfiddle.net/goqcev42/

Within the nested describe() scope, the orchard object is undefined, even though it's defined within the it() blocks within it.

Is this intentional on the part of Jasmine's developers, possibly to avoid issues with resetting the object in beforeEach() and possible breaking some references? How do they make it happen? I could see how this might be useful, I'm just very curious as to how it works. (My guess is some apply() or call() magic, but I'm not sure how...)

--

As a side-note, I can still DRY up my code by simply using another beforeEach() block:

describe('simple object', function () {
    var orchard;

    beforeEach(function () {
        orchard = {
            trees: {
                apple: 10,
                orange : 20
            },
            bushes: {
                boysenberry : 40,
                blueberry: 35
            }
        };
    });

    describe('trees', function () {
        var trees;

        beforeEach(function() {
            trees = orchard.trees;
        });

        it ('should have apples and oranges', function() {
            expect (trees.apple).toBeDefined();
            expect (trees.orange).toBeDefined();

            expect (trees.apple).toEqual(10);
            expect (trees.orange).toEqual(20);
        });
        it ('should NOT have pears or cherries', function() {
            expect (trees.pear).toBeUndefined();
            expect (trees.cherry).toBeUndefined();
        });
    });
});
Ken Bellows
  • 6,711
  • 13
  • 50
  • 78
  • 7
    Use a debugger to trace the flow of execution through your program, you'll find that the `beforeEach` executes before each `it`, **not** once before the entire `describe`. This is the whole point of `beforeEach`, it's **before each test case**. – user229044 Feb 16 '15 at 16:59
  • @Andrew Eisenberg, what if I want to call a helper JS function that has 'it' tests in it and those tests require trees ? I want to be able to call it from describe because I cannot call a it from a it. What are my options in this case ? – TechCrunch Feb 23 '16 at 04:35
  • 1
    @TechCrunch What do you mean by "trees"? Generally if you need to initialize some data structures or test inputs before you run any `it()` statements you'd want to use a `beforeEach()` block. If you need some code to run exactly once for the whole `describe()`, rather than once per `it()`, use `beforeAll()`. Details here: http://jasmine.github.io/2.1/introduction.html#section-Setup_and_Teardown – Ken Bellows Feb 23 '16 at 12:42
  • @KenB, I'm referring to trees as in your example code. I posted my question at http://stackoverflow.com/questions/35568104/karma-jasmine-execute-a-test-from-a-helper-function. – TechCrunch Feb 23 '16 at 18:19
  • broken fiddles. – Kurkula Aug 31 '17 at 17:20
  • @Kurkula thanks, fixed – Ken Bellows Aug 31 '17 at 18:29
  • Possibly useful: https://stackoverflow.com/q/52512309/28324 – Elias Zamaria Dec 06 '20 at 01:58

3 Answers3

72

The body of a describe block is executed before the beforeEach blocks.

This is exactly as expected. The problem is that your var trees variable is trying to access orchard before it has been initialized. The body of a describe block is executed before the beforeEach blocks. To solve this problem, the third code snippet is the only way to go.

Jasmine will first execute the describe blocks, and then execute the beforeEach blocks before running each test.

Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
Andrew Eisenberg
  • 28,387
  • 9
  • 92
  • 148
  • What if you have a bunch of describe statements in a file, but need to initialize the same things in a beforeEach for every describe block? Is there any way to declare the initialization once? – Healforgreen Jan 11 '16 at 17:02
  • The closest thing I can think of is to add something to your karma configuration file. – Andrew Eisenberg Jan 11 '16 at 20:06
  • 3
    @Nocomm Generally the answer is to group all describes that need a shared beforeEach() within a master describe() that has that beforeEach(). Nothing wrong with nesting describes within other describes! – Ken Bellows Jan 11 '16 at 21:46
  • @kenbellows what is the difference between what you are describing here and OP's second code block? – michaelAdam Jul 06 '16 at 14:35
  • @michaelAdam Do you mean the third code block, or do you really mean the second block? The third block is exactly what I'm describing here – Ken Bellows Jul 06 '16 at 15:02
4

Well you could still initialize variables outside the beforeEach block. I generally do it for constants and still remain DRY without introducing beforeEach blocks.

describe('simple object', function () {
    const orchard = {
        trees: {
            apple: 10,
            orange: 20
        },
        bushes: {
            boysenberry: 40,
            blueberry: 35
        }
    };


    describe('trees', function () {
        const trees = orchard.trees;

        it('should have apples and oranges', function () {


            expect(trees.apple).toBeDefined();
            expect(trees.orange).toBeDefined();

            expect(trees.apple).toEqual(10);
            expect(trees.orange).toEqual(20);
        });
        it('should NOT have pears or cherries', function () {
            var trees = orchard.trees;

            expect(trees.pear).toBeUndefined();
            expect(trees.cherry).toBeUndefined();
        });
    });
});
aeje
  • 239
  • 1
  • 9
  • Even with `const`, that setup is somewhat risky since `trees` and `bushes` are not const themselves. The only way to do it safely is deep freezing the object. – Tahsis Claus Sep 18 '17 at 20:21
3

Lets take the third code snippet. Further, it can be refactored as below:

describe('simple object', function () {
    var orchard;

    beforeEach(function () {
        orchard = {
            trees: {
                apple: 10,
                orange : 20
            },
            bushes: {
                boysenberry : 40,
                blueberry: 35
            }
        };
    });

    describe('trees', function () {

        it ('should have apples and oranges', function() {
            expect (orchard.trees.apple).toBeDefined();
            expect (orchard.trees.orange).toBeDefined();

            expect (orchard.trees.apple).toEqual(10);
            expect (orchard.trees.orange).toEqual(20);
        });
        it ('should NOT have pears or cherries', function() {
            expect (orchard.trees.pear).toBeUndefined();
            expect (orchard.trees.cherry).toBeUndefined();
        });
    });
});

For the new comers to Jasmine, this is how you intrepret the above code :\

  1. describe defines a test suite. The test suite name here is a user defined simple string, say "simple object".
  2. A test suite can itself contain other test suites, meaning describecan contain nested suites.
  3. Just like other programming languages, orchid is global to all the functions & suites defined within simple object test suite.
  4. It block is called a specification or a SPEC. It blocks contain individual tests.
  5. Just when Jasmine executes the test cases, it will first visit the it blocks meaning it will traverse all the it block declarations.
  6. When Jasmine actually executes test cases, it will check for beforeEach function and hence orchard gets trees value assigned to it.
  7. And hence you need not write a beforeEach function, inside a sub suite. You can simply ignore

    beforeEach (function() { trees = orchard.trees; });

  8. Now compare the latest snippet below with the third snippet above.

  • This is of course a perfectly valid way to write the code that gets around the problem, but the point of my question was to find a good way to avoid writing `orchard.trees` in each `expect()`. This is a simplistic example, but imagine you were testing some deep sub-object like `farmers[0].farms[5].products.orchard.trees`. That's going to become unreadable pretty quickly. And even beside that, the point of `describe("trees")` is to describe the `trees` object directly, so, IMHO, it's more in line with the spirit of the BDD style to pull out the specific thing you're testing. Thoughts? – Ken Bellows Oct 25 '17 at 11:20
  • If the need for testing out deep sub objects, then your third code snippet looks good for me :) – now he who must not be named. Oct 27 '17 at 09:07
  • I would argue that the `beforeEach` should really be `beforeAll` since you're not changing the data. There's no reason to run that before each test. – Joshua Pinter Jun 29 '20 at 22:31
  • Could you also not use `this.orchard` instead of declaring `var orchard`? – Joshua Pinter Jun 29 '20 at 22:32