Skip to content

Commit

Permalink
fix(expect,internal,testing): support expect.assertions (#6032)
Browse files Browse the repository at this point in the history
Co-authored-by: Yoshiya Hinosawa <[email protected]>
Co-authored-by: Asher Gomez <[email protected]>
  • Loading branch information
3 people authored Oct 25, 2024
1 parent 10ee73d commit ed79df4
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 52 deletions.
33 changes: 0 additions & 33 deletions expect/_assertion_test.ts

This file was deleted.

5 changes: 5 additions & 0 deletions expect/_assertion.ts → expect/_assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export function hasAssertions() {
assertionState.setAssertionCheck(true);
}

export function assertions(num: number) {
assertionState.setAssertionCount(num);
}

export function emitAssertionTrigger() {
assertionState.setAssertionTriggered(true);
assertionState.updateAssertionTriggerCount();
}
64 changes: 64 additions & 0 deletions expect/_assertions_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

import { describe, it, test } from "@std/testing/bdd";
import { expect } from "./expect.ts";

Deno.test("expect.hasAssertions() API", () => {
describe("describe suite", () => {
// FIXME(eryue0220): This test should through `toThrowErrorMatchingSnapshot`
it("should throw an error", () => {
expect.hasAssertions();
});

it("should pass", () => {
expect.hasAssertions();
expect("a").toEqual("a");
});
});

it("it() suite should pass", () => {
expect.hasAssertions();
expect("a").toEqual("a");
});

// FIXME(eryue0220): This test should through `toThrowErrorMatchingSnapshot`
test("test suite should throw an error", () => {
expect.hasAssertions();
});

test("test suite should pass", () => {
expect.hasAssertions();
expect("a").toEqual("a");
});
});

Deno.test("expect.assertions() API", () => {
test("should pass", () => {
expect.assertions(2);
expect("a").not.toBe("b");
expect("a").toBe("a");
});

// FIXME(eryue0220): This test should through `toThrowErrorMatchingSnapshot`
test("should throw error", () => {
expect.assertions(1);
expect("a").not.toBe("b");
expect("a").toBe("a");
});

it("redeclare different assertion count", () => {
expect.assertions(3);
expect("a").not.toBe("b");
expect("a").toBe("a");
expect.assertions(2);
});

test("expect no assertions", () => {
expect.assertions(0);
});

// FIXME(eryue0220): This test should through `toThrowErrorMatchingSnapshot`
it("should throw an error", () => {
expect.assertions(2);
});
});
27 changes: 26 additions & 1 deletion expect/expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import type {
Matchers,
} from "./_types.ts";
import { AssertionError } from "@std/assert/assertion-error";
import { emitAssertionTrigger, hasAssertions } from "./_assertion.ts";
import {
assertions,
emitAssertionTrigger,
hasAssertions,
} from "./_assertions.ts";
import {
addCustomEqualityTesters,
getCustomEqualityTesters,
Expand Down Expand Up @@ -491,6 +495,7 @@ expect.stringContaining = asymmetricMatchers.stringContaining as (
expect.stringMatching = asymmetricMatchers.stringMatching as (
pattern: string | RegExp,
) => ReturnType<typeof asymmetricMatchers.stringMatching>;

/**
* `expect.hasAssertions` verifies that at least one assertion is called during a test.
*
Expand All @@ -509,6 +514,26 @@ expect.stringMatching = asymmetricMatchers.stringMatching as (
* ```
*/
expect.hasAssertions = hasAssertions as () => void;

/**
* `expect.assertions` verifies that a certain number of assertions are called during a test.
*
* Note: expect.assertions only can use in bdd function test suite, such as `test` or `it`.
*
* @example
* ```ts
*
* import { test } from "@std/testing/bdd";
* import { expect } from "@std/expect";
*
* test("it works", () => {
* expect.assertions(1);
* expect("a").not.toBe("b");
* });
* ```
*/
expect.assertions = assertions as (num: number) => void;

/**
* `expect.objectContaining(object)` matches any received object that recursively matches the expected properties.
* That is, the expected object is not a subset of the received object. Therefore, it matches a received object
Expand Down
2 changes: 1 addition & 1 deletion expect/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
* - {@linkcode expect.stringContaining}
* - {@linkcode expect.stringMatching}
* - Utilities:
* - {@linkcode expect.assertions}
* - {@linkcode expect.addEqualityTester}
* - {@linkcode expect.extend}
* - {@linkcode expect.hasAssertions}
Expand All @@ -72,7 +73,6 @@
* - Asymmetric matchers:
* - `expect.not.objectContaining`
* - Utilities:
* - `expect.assertions`
* - `expect.addSnapshotSerializer`
*
* The tracking issue to add support for unsupported parts of the API is
Expand Down
121 changes: 112 additions & 9 deletions internal/assertion_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,55 @@
*/
export class AssertionState {
#state: {
assertionCount: number | undefined;
assertionCheck: boolean;
assertionTriggered: boolean;
assertionTriggeredCount: number;
};

constructor() {
this.#state = {
assertionCount: undefined,
assertionCheck: false,
assertionTriggered: false,
assertionTriggeredCount: 0,
};
}

/**
* Get the number that through `expect.assertions` api set.
*
* @returns the number that through `expect.assertions` api set.
*
* @example Usage
* ```ts ignore
* import { AssertionState } from "@std/internal";
*
* const assertionState = new AssertionState();
* assertionState.assertionCount;
* ```
*/
get assertionCount(): number | undefined {
return this.#state.assertionCount;
}

/**
* Get a certain number that assertions were called before.
*
* @returns return a certain number that assertions were called before.
*
* @example Usage
* ```ts ignore
* import { AssertionState } from "@std/internal";
*
* const assertionState = new AssertionState();
* assertionState.assertionTriggeredCount;
* ```
*/
get assertionTriggeredCount(): number {
return this.#state.assertionTriggeredCount;
}

/**
* If `expect.hasAssertions` called, then through this method to update #state.assertionCheck value.
*
Expand Down Expand Up @@ -57,6 +95,41 @@ export class AssertionState {
this.#state.assertionTriggered = val;
}

/**
* If `expect.assertions` called, then through this method to update #state.assertionCheck value.
*
* @param num Set #state.assertionCount's value, for example if the value is set 2, that means
* you must have two assertion matchers call in your test suite.
*
* @example Usage
* ```ts ignore
* import { AssertionState } from "@std/internal";
*
* const assertionState = new AssertionState();
* assertionState.setAssertionCount(2);
* ```
*/
setAssertionCount(num: number) {
this.#state.assertionCount = num;
}

/**
* If any matchers was called, `#state.assertionTriggeredCount` value will plus one internally.
*
* @example Usage
* ```ts ignore
* import { AssertionState } from "@std/internal";
*
* const assertionState = new AssertionState();
* assertionState.updateAssertionTriggerCount();
* ```
*/
updateAssertionTriggerCount() {
if (this.#state.assertionCount !== undefined) {
this.#state.assertionTriggeredCount += 1;
}
}

/**
* Check Assertion internal state, if `#state.assertionCheck` is set true, but
* `#state.assertionTriggered` is still false, then should throw an Assertion Error.
Expand All @@ -69,26 +142,56 @@ export class AssertionState {
* import { AssertionState } from "@std/internal";
*
* const assertionState = new AssertionState();
* if (assertionState.checkAssertionErrorStateAndReset()) {
* if (assertionState.checkAssertionErrorState()) {
* // throw AssertionError("");
* }
* ```
*/
checkAssertionErrorStateAndReset(): boolean {
const result = this.#state.assertionCheck &&
!this.#state.assertionTriggered;

this.#resetAssertionState();

return result;
checkAssertionErrorState(): boolean {
return this.#state.assertionCheck && !this.#state.assertionTriggered;
}

#resetAssertionState(): void {
/**
* Reset all assertion state when every test suite function ran completely.
*
* @example Usage
* ```ts ignore
* import { AssertionState } from "@std/internal";
*
* const assertionState = new AssertionState();
* assertionState.resetAssertionState();
* ```
*/
resetAssertionState(): void {
this.#state = {
assertionCount: undefined,
assertionCheck: false,
assertionTriggered: false,
assertionTriggeredCount: 0,
};
}

/**
* Check Assertion called state, if `#state.assertionCount` is set to a number value, but
* `#state.assertionTriggeredCount` is less then it, then should throw an assertion error.
*
* @returns a boolean value, that the test suite is satisfied with the check. If not,
* it should throw an AssertionError.
*
* @example Usage
* ```ts ignore
* import { AssertionState } from "@std/internal";
*
* const assertionState = new AssertionState();
* if (assertionState.checkAssertionCountSatisfied()) {
* // throw AssertionError("");
* }
* ```
*/
checkAssertionCountSatisfied(): boolean {
return this.#state.assertionCount !== undefined &&
this.#state.assertionCount !== this.#state.assertionTriggeredCount;
}
}

const assertionState = new AssertionState();
Expand Down
14 changes: 7 additions & 7 deletions internal/assertion_state_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@
import { assertEquals } from "@std/assert";
import { AssertionState } from "./assertion_state.ts";

Deno.test("AssertionState checkAssertionErrorStateAndReset pass", () => {
Deno.test("AssertionState checkAssertionErrorState pass", () => {
const assertionState = new AssertionState();
assertionState.setAssertionTriggered(true);

assertEquals(assertionState.checkAssertionErrorStateAndReset(), false);
assertEquals(assertionState.checkAssertionErrorState(), false);
});

Deno.test("AssertionState checkAssertionErrorStateAndReset pass", () => {
Deno.test("AssertionState checkAssertionErrorState pass", () => {
const assertionState = new AssertionState();
assertionState.setAssertionTriggered(true);

assertEquals(assertionState.checkAssertionErrorStateAndReset(), false);
assertEquals(assertionState.checkAssertionErrorState(), false);

assertionState.setAssertionCheck(true);
assertEquals(assertionState.checkAssertionErrorStateAndReset(), true);
assertEquals(assertionState.checkAssertionErrorState(), false);
});

Deno.test("AssertionState checkAssertionErrorStateAndReset fail", () => {
Deno.test("AssertionState checkAssertionErrorState fail", () => {
const assertionState = new AssertionState();
assertionState.setAssertionCheck(true);

assertEquals(assertionState.checkAssertionErrorStateAndReset(), true);
assertEquals(assertionState.checkAssertionErrorState(), true);
});
Loading

0 comments on commit ed79df4

Please sign in to comment.