Question

Assume I have a simple page, whereby clicking the page title toggles the visibility of the page content (realistic? no, but it's a simple test that includes DOM elements). I'll spare you the HTML and JS implementation as I'm sure you can see it in your mind.

I'm trying to test this using jasmine and I'm running into code duplication issues, primarily around the separation of context (how this test is different than its bretheren) and the trigger (the action that is being tested and capturing of the results)

describe("Home", function () {
    describe("Selecting the page title", function () {
        beforeEach(function () {
            loadFixtures('Home.fixture.htm');
        });

        describe("when page content is visible", function () {
            it("should hide page content", function () {
                $('#pageTitle').trigger('click');

                expect($('#pageContent')).toBeHidden();
            });
        });

        describe("when page content is hidden", function () {
            it("should show page content", function () {
                $('#pageContent').hide();
                $('#pageTitle').trigger('click');

                expect($('#pageContent')).toBeVisible();
            });
        });
    });
});

How can I separate the trigger (raising of 'click' in this case) from the context (shared = loading the fixture, specific = hiding the page content) to avoid code duplication?

If it helps, here's what I would do in MSpec (a context/spec framework for .NET):

[Subject("Home")]
class when_selecting_the_page_title_when_page_content_is_visible : HomeContext
{
    It should_hide_page_content = () =>
        // Assert that page content is hidden
}

[Subject("Home")]
class when_selecting_the_page_title_when_page_content_is_hidden : HomeContext
{
    Establish context = () =>
        // Hide page content

    It should_show_page_content = () =>
        // Assert that page content is visible
}

class HomeContext
{
    Establish context = () =>
        // Load the fixture

    Because of = () =>
        // Fire the event
}

Disclaimer: I'm not trying to argue C# vs Javascript or MSpec vs whatever here, it's just to provide an example of the code reuse I'm after. I've also skipped some features of MSpec to keep the example simple.

Was it helpful?

Solution

Well that's why describe blocks can be nested, and beforeEach() can be called at any level which only applies to that level and under.

I wouldn't worry too much about code duplication in tests. More indirection means less readability. Verbosity in tests is usually a good thing, within reason. Too many specific macro function that only apply to tiny pieces of your code and you end up with a gigantic ball of brittle tests that few people can figure out how to change.

describe("Home", function () {
    describe("Selecting the page title", function () {
        beforeEach(function () {
            loadFixtures('Home.fixture.htm');
        });

        describe("when page content is visible", function () {
            beforeEach(function() {
                $('#pageTitle').trigger('click');
            });

            it("should hide page content", function () {
                expect($('#pageContent')).toBeHidden();
            });
        });

        describe("when page content is hidden", function () {
            beforeEach(function() {
                $('#pageContent').hide();
                $('#pageTitle').trigger('click');
            });

            it("should show page content", function () {
                expect($('#pageContent')).toBeVisible();
            });
        });
    });
});

Or, if you must, setup a helper macro function in that describe block, but that can get ugly fast. It starts putting too much logic in your tests. Eventually you need tests for your tests. Yo dawg...

describe("Home", function () {
    describe("Selecting the page title", function () {
        beforeEach(function () {
            this.loadHomeFixture = function(options) {
                options = options || {};
                loadFixtures('Home.fixture.htm');
                if (options.hidden) {
                    $('#pageContent').hide();
                }
            };
        });

        describe("when page content is visible", function () {
            it("should hide page content", function () {
                this.loadHomeFixture({ hidden: false });
                expect($('#pageContent')).toBeHidden();
            });
        });

        describe("when page content is hidden", function () {
            it("should show page content", function () {
                this.loadHomeFixture({ hidden: true });
                expect($('#pageContent')).toBeVisible();
            });
        });
    });
});

Also, sometimes I organize tests by state first, then feature. Allowing you to more cleanly separate the initial state from the action.

Here's how you might do that in outline form.

  • Home
    • beforeEach: load home fixture
    • when page content is visible
      • beforeEach: make content visible
      • selecting the page title
        • beforeEach: click page title
        • should hide page content
          • assert page content is hidden
      • other action that does something when page content is visible
    • when page content is hidden
      • beforeEach: make content hidden
      • selecting the page title
        • beforeEach: click page title
        • should show page content
          • assert page content is visible
      • other action that does something when page content is hidden

This structure allows you to drill in based on initial state, so if you had more features that depended on the visibility on the page, it's easy to test those as you already have a place with that state setup where you can drop the new test.

Though testing can be more like a matrix (state × features) but most testing systems give us a tree. Whether you branch from that tree by state or feature is sort of up to you and depends on which has the higher order of complexity.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top