你的第一个套件

套件:describe 测试说明

describe 函数用于对相关的规范进行分组,通常每个测试文件在顶层都有一个规范。字符串参数用于命名规范集合,并将与规范连接以构成规范的全名。这有助于在大型套件中查找规范。如果您对它们进行了恰当命名,您的规范在传统的 BDD 样式中可读作完整句子。

规范

规范通过调用全局 Jasmine 函数 it 来定义,与 describe 一样,它需要一个字符串和一个函数。该字符串是规范的标题,而函数是规范或测试。规范包含一个或多个对代码状态进行测试的期望值。Jasmine 中的期望值是对真或假的断言。具有所有真期望值的规范是一个通过的规范。具有一个或多个假期望值的规范是一个失败的规范。

describe("A suite", function() {
    it("contains spec with an expectation", function() {
        expect(true).toBe(true);
    });
});

它只是函数

由于 describeit 块是函数,因此它们可以包含实现测试所需的任何可执行代码。适用 JavaScript 作用域规则,因此在 describe 中声明的变量可用于套件中的任何 it 块。

describe("A suite is just a function", function() {
    let a;

    it("and so is a spec", function() {
        a = true;

        expect(a).toBe(true);
    });
});

期望值

期望值通过函数 expect 构建,该函数接受一个值,称为实际值。它与 Matcher 函数链接,后者接受预期值。

describe("The 'toBe' matcher compares with ===", function() {

Matcher

每个 Matcher 都实现实际值和预期值之间的布尔比较。它负责向 Jasmine 报告期望值是真还是假。Jasmine 随后会通过或使规范失败。

    it("and has a positive case", function() {
        expect(true).toBe(true);
    });

任何 Matcher 都可以通过在调用 Matcher 之前将对 expect 的调用与 not 链接,来评估为否定断言。

    it("and can have a negative case", function() {
        expect(false).not.toBe(true);
    });

Jasmine 随附一组丰富的 Matcher,您可以在 API 文档 中找到完整列表。还可以在项目的域需要 Jasmine 中未包含的特定断言时,编写自定义 Matcher

});

设置和拆除

为了帮助测试套件清除任何重复的设置和拆除代码,Jasmine 提供了全局 beforeEachafterEachbeforeAllafterAll 函数。

describe("A suite with some shared setup", function() {
    let foo = 0;

顾名思义,beforeEach 函数在调用它的 describe 中的每个规范之前调用一次

    beforeEach(function() {
        foo += 1;
    });

并且 afterEach 函数在每个规范之后调用一次。

    afterEach(function() {
        foo = 0;
    });

beforeAll 函数只在 describe 中的所有规范运行前调用一次

    beforeAll(function() {
        foo = 1;
    });

afterAll 函数在所有规范完成后调用

    afterAll(function() {
        foo = 0;
    });
});

beforeAllafterAll 可用于加快带有昂贵的设置和拆卸的测试套件速度。

但请小心使用 beforeAllafterAll!由于它们不会在规范之间重置,因此很容易在规范之间意外泄露状态,从而错误地通过或失败。

this 关键字

另一种通过 this 关键字在 beforeEachitafterEach 之间共享变量的方法。每个规范的 beforeEach/it/afterEach 具有 this,为与下一个规范的 beforeEach/it/afterEach 相同的空对象。

注意:如果希望使用 this 关键字共享变量,则必须使用 function 关键字,而不能使用箭头函数。

describe("A spec", function() {
    beforeEach(function() {
        this.foo = 0;
    });

    it("can use the `this` to share state", function() {
        expect(this.foo).toEqual(0);
        this.bar = "test pollution?";
    });

    it("prevents test pollution by having an empty `this` " +
            "created for the next spec", function() {
        expect(this.foo).toEqual(0);
        expect(this.bar).toBe(undefined);
    });
});

使用 fail 手动使规范失败

fail 函数会导致规范失败。它可以将失败消息或 Error 对象作为参数。

describe("A spec using the fail function", function() {
    function foo(x, callBack) {
        if (x) {
            callBack();
        }
    };

    it("should not call the callBack", function() {
        foo(false, function() {
            fail("Callback has been called");
        });
    });
});

嵌套 describe

describe 的调用可以嵌套,并可在任何级别定义规范。这允许将套件组合为函数树。在执行规范之前,Jasmine 将向下遍历树,按顺序执行每个 beforeEach 函数。在执行规范之后,Jasmine 将以类似方式遍历 afterEach 函数。

describe("A spec", function() {
    let foo;

    beforeEach(function() {
        foo = 0;
        foo += 1;
    });

    afterEach(function() {
        foo = 0;
    });

    it("is just a function, so it can contain any code", function() {
        expect(foo).toEqual(1);
    });
    
    it("can have more than one expectation", function() {
        expect(foo).toEqual(1);
        expect(true).toEqual(true);
    });

    describe("nested inside a second describe", function() {
        let bar;

        beforeEach(function() {
          bar = 1;
        });
    
        it("can reference both scopes as needed", function() {
          expect(foo).toEqual(bar);
        });
    });
});

禁用套件

可以使用 xdescribe 函数禁用套件。运行时,这些套件及其内部的任何规范将被跳过,其结果将显示为待定。

套件也可以通过 fdescribe 函数进行聚焦。这意味着只有 fdescribe 套件将运行。

xdescribe("A spec", function() {
    let foo;
    
    beforeEach(function() {
        foo = 0;
        foo += 1;
    });
    
    it("is just a function, so it can contain any code", function() {
        expect(foo).toEqual(1);
    });
});

挂起的规范

挂起的规范不会运行,但其名称将在结果中显示为 挂起

describe("Pending specs", function() {

xit 声明的任何规范都标记为挂起。

    xit("can be declared 'xit'", function() {
        expect(true).toBe(false);
    });

在结果中,任何没有函数体的声明的规范也都将标记为挂起。

    it("can be declared with 'it' but without a function");

而且,如果在规范主体中的任何位置调用函数 pending,无论期望如何,规范都将标记为待定。传递给 pending 的字符串将被视为原因,并在套件完成后显示。

测试也可以通过 fit 函数进行聚焦。这意味着只有 fit 测试将运行。

    it("can be declared by calling 'pending' in the spec body", function() {
        expect(true).toBe(false);
        pending('this is why it is pending');
    });
});

间谍

Jasmine 有称为 间谍 的测试双函数。间谍可以存根任何函数,并跟踪对该函数及其所有参数的调用。间谍仅存在于其被定义的 describeit 块中,并且在每个规范后都会被移除。有专用于与间谍交互的匹配器。

describe("A spy", function() {
    let foo;
    let bar = null;

    beforeEach(function() {
        foo = {
            setBar: function(value) {
                bar = value;
            }
        };

您可以使用 and 来定义间谍在被调用时将执行的操作。

        spyOn(foo, 'setBar');

        foo.setBar(123);
        foo.setBar(456, 'another param');
    });

如果调用了间谍,则 toHaveBeenCalled 匹配器将通过。

    it("tracks that the spy was called", function() {
        expect(foo.setBar).toHaveBeenCalled();
    });

如果间谍被调用指定次数,则 toHaveBeenCalledTimes 匹配器将通过。

    it("tracks that the spy was called x times", function() {
        expect(foo.setBar).toHaveBeenCalledTimes(2);
    });

只有当参数列表与对该监视器记录的任何调用相匹配时,toHaveBeenCalledWith 匹配器才会返回 true。

    it("tracks all the arguments of its calls", function() {
        expect(foo.setBar).toHaveBeenCalledWith(123);
        expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');
    });

    it("stops all execution on a function", function() {
        expect(bar).toBeNull();
    });

您可以使用 calls 获取有关监视器对其调用所跟踪的所有数据。

    it("tracks if it was called at all", function() {
        foo.setBar();

        expect(foo.setBar.calls.any()).toEqual(true);
    });
});

监视器:createSpy

如果没有要监视的函数,jasmine.createSpy 可以创建一个“空白”的监视器。此监视器将充当任何其他监视器,即跟踪调用、参数等,但其背后没有实现。

describe("A spy, when created manually", function() {
    let whatAmI;

    beforeEach(function() {
        whatAmI = jasmine.createSpy('whatAmI');

        whatAmI("I", "am", "a", "spy");
    });

    it("tracks that the spy was called", function() {
        expect(whatAmI).toHaveBeenCalled();
    });
});

监视器:createSpyObj

为了使用多个监视器创建模拟,请使用 jasmine.createSpyObj 并传入一个字符串数组。它将返回一个对象,其中每个字符串都具有一个作为监视器的属性。

describe("Multiple spies, when created manually", function() {
    let tape;

    beforeEach(function() {
        tape = jasmine.createSpyObj(
            'tape',
            ['play', 'pause', 'stop', 'rewind']
        );

        tape.play();
        tape.pause();
        tape.rewind(0);
    });

    it("creates spies for each requested function", function() {
        expect(tape.play).toBeDefined();
        expect(tape.pause).toBeDefined();
        expect(tape.stop).toBeDefined();
        expect(tape.rewind).toBeDefined();
    });
});

更加精细的匹配

有时您不想进行完全相等匹配。Jasmine 提供了许多非对称相等测试器。

describe("Matching with finesse", function() {

jasmine.any 将构造函数或“类”名称作为预期值。如果构造函数与实际值的构造函数匹配,它将返回 true

    describe("jasmine.any", function() {
        it("matches any value", function() {
            expect({}).toEqual(jasmine.any(Object));
            expect(12).toEqual(jasmine.any(Number));
        });
    
        describe("when used with a spy", function() {
            it("is useful for comparing arguments", function() {
                const foo = jasmine.createSpy('foo');
                foo(12, function() {
                    return true;
                });
    
                expect(foo).toHaveBeenCalledWith(
                    jasmine.any(Number), jasmine.any(Function)
                );
            });
        });
    });

jasmine.anything 在实际值不是 nullundefined 时返回 true

    describe("jasmine.anything", function() {
        it("matches anything", function() {
            expect(1).toEqual(jasmine.anything());
        });

        describe("when used with a spy", function() {
            it("is useful when the argument can be ignored", function() {
                const foo = jasmine.createSpy('foo');
                foo(12, function() {
                    return false;
                });

                expect(foo).toHaveBeenCalledWith(12, jasmine.anything());
            });
        });
    });

jasmine.objectContaining 适用于期望只关注实际中某些键值对的情况。

    describe("jasmine.objectContaining", function() {
        let foo;

        beforeEach(function() {
            foo = {
                a: 1,
                b: 2,
                bar: "baz"
            };
        });

        it("matches objects with the expect key/value pairs", function() {
            expect(foo).toEqual(jasmine.objectContaining({
                bar: "baz"
            }));
            expect(foo).not.toEqual(jasmine.objectContaining({
                c: 37
            }));
        });

        describe("when used with a spy", function() {
            it("is useful for comparing arguments", function() {
                const callback = jasmine.createSpy('callback');

                callback({
                    bar: "baz"
                });

                expect(callback).toHaveBeenCalledWith(
                    jasmine.objectContaining({ bar: "baz" })
                );
            });
        });
    });

jasmine.arrayContaining 适用于期望只关注数组中某些值的情况。

    describe("jasmine.arrayContaining", function() {
        let foo;

        beforeEach(function() {
            foo = [1, 2, 3, 4];
        });

        it("matches arrays with some of the values", function() {
            expect(foo).toEqual(jasmine.arrayContaining([3, 1]));
            expect(foo).not.toEqual(jasmine.arrayContaining([6]));
        });

        describe("when used with a spy", function() {
            it("is useful when comparing arguments", function() {
                const callback = jasmine.createSpy('callback');

                callback([1, 2, 3, 4]);

                expect(callback).toHaveBeenCalledWith(
                    jasmine.arrayContaining([4, 2, 3])
                );
                expect(callback).not.toHaveBeenCalledWith(
                    jasmine.arrayContaining([5, 2])
                );
            });
        });
    });

jasmine.stringMatching 适用于不想在更大的对象中精确匹配字符串或在监视器期望中匹配字符串的一部分的情况。

    describe('jasmine.stringMatching', function() {
        it("matches as a regexp", function() {
            expect({foo: 'bar'}).toEqual({
                foo: jasmine.stringMatching(/^bar$/)
            });
            expect({foo: 'foobarbaz'}).toEqual({
                foo: jasmine.stringMatching('bar')
            });
        });

        describe("when used with a spy", function() {
            it("is useful for comparing arguments", function() {
                const callback = jasmine.createSpy('callback');

                callback('foobarbaz');

                expect(callback).toHaveBeenCalledWith(
                    jasmine.stringMatching('bar')
                );
                expect(callback).not.toHaveBeenCalledWith(
                    jasmine.stringMatching(/^bar$/)
                );
            });
        });
    });

自定义非对称相等测试器

当您需要检查某个内容满足特定条件而不完全相等时,您还可以通过提供一个具有 asymmetricMatch 函数的对象来指定一个自定义非对称相等测试器。

    describe("custom asymmetry", function() {
        const tester = {
            asymmetricMatch: function(actual) {
                const secondValue = actual.split(',')[1];
                return secondValue === 'bar';
            }
        };

        it("dives in deep", function() {
            expect("foo,bar,baz,quux").toEqual(tester);
        });

        describe("when used with a spy", function() {
            it("is useful for comparing arguments", function() {
                const callback = jasmine.createSpy('callback');

                callback('foo,bar,baz');

                expect(callback).toHaveBeenCalledWith(tester);
            });
        });
    });
});

Jasmine Clock

可以使用 Jasmine Clock 来测试依赖时间的代码。

describe("Manually ticking the Jasmine Clock", function() {
    let timerCallback;

在需要控制时间的规范或套件中使用 jasmine.clock().install 调用对其进行安装。

    beforeEach(function() {
        timerCallback = jasmine.createSpy("timerCallback");
        jasmine.clock().install();
    });

完成后一定要卸载时钟,以还原原始函数。

    afterEach(function() {
        jasmine.clock().uninstall();
    });

模拟 JavaScript 超时函数

您可以使 setTimeoutsetInterval 同步执行注册的函数,只需在时钟向前推进时执行一次。

要执行注册的函数,请通过 jasmine.clock().tick 函数向前推进时间,该函数会获取以毫秒为单位的时间。

    it("causes a timeout to be called synchronously", function() {
        setTimeout(function() {
            timerCallback();
        }, 100);

        expect(timerCallback).not.toHaveBeenCalled();

        jasmine.clock().tick(101);

        expect(timerCallback).toHaveBeenCalled();
    });

    it("causes an interval to be called synchronously", function() {
        setInterval(function() {
            timerCallback();
        }, 100);

        expect(timerCallback).not.toHaveBeenCalled();

        jasmine.clock().tick(101);
        expect(timerCallback.calls.count()).toEqual(1);

        jasmine.clock().tick(50);
        expect(timerCallback.calls.count()).toEqual(1);

        jasmine.clock().tick(50);
        expect(timerCallback.calls.count()).toEqual(2);
    });

模拟 Date

Jasmine Clock 还可用于模拟当前日期。

    describe("Mocking the Date object", function(){
        it("mocks the Date object and sets it to a given time", function() {
            const baseTime = new Date(2013, 9, 23);

如果您不向 mockDate 提供基准时间,它将使用当前日期。

            jasmine.clock().mockDate(baseTime);

            jasmine.clock().tick(50);
            expect(new Date().getTime()).toEqual(baseTime.getTime() + 50);
        });
    });
});

异步支持

Jasmine 还支持运行需要测试异步操作的规范。 传递给 beforeAllafterAllbeforeEachafterEachit 的函数可以声明为 async

Jasmine 还支持显式返回 Promise 或接受回调的异步函数。有关更多信息,请参阅 异步工作教程

describe("Using async/await", function() {
    beforeEach(async function() {
        await soon();
        value = 0;
    });

此规范将在从上述 beforeEach 调用返回的 Promise 解决后才会启动。 而此规范将在他返回的 Promise 解决后才会完成。

    it("supports async execution of test preparation and expectations",
        async function() {
            await soon();
            value++;
            expect(value).toBeGreaterThan(0);
        }
    );
});

默认情况下,jasmine 会等待异步规范 5 秒钟才能完成,然后再导致超时失败。 如果超时在调用 done 前到期,当前规范将标记为失败,且套件执行将继续执行,就像已调用 done 一样。

如果特定规范应更快地失败或需要更多时间,可以通过向 it 等传递超时值进行调整。

如果整个套件应具有不同的超时,则可以在任何给定的 describe 之外全局设置 jasmine.DEFAULT_TIMEOUT_INTERVAL

describe("long asynchronous specs", function() {
    beforeEach(async function() {
        await somethingSlow();
    }, 1000);

    it("takes a long time", function() {
        await somethingReallySlow();
    }, 10000);

    afterEach(function() {
        await somethingSlow();
        }, 1000);
    });

    function soon() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve();
            }, 1);
        });
    }
});