From 2233216b3a514f4d88e517f6e8f88a3152443cf7 Mon Sep 17 00:00:00 2001
From: Edward Bebbington
Date: Mon, 25 Oct 2021 22:37:45 +0100
Subject: [PATCH 01/62] BREAKING: Remove rhum runner
---
.github/workflows/master.yml | 10 +-
README.md | 36 +-
deps.ts | 7 -
egg.json | 4 +-
example_tests/basic/1.ts | 29 --
example_tests/basic/1_fail.ts | 29 --
example_tests/basic/2.ts | 47 ---
example_tests/basic/3.ts | 32 --
example_tests/basic/tests_fail.ts | 3 -
example_tests/basic/tests_pass.ts | 3 -
mod.ts | 390 --------------------
src/interfaces.ts | 131 -------
src/mock_builder.ts | 164 --------
src/mocks/server_request.ts | 59 ---
src/rhum_asserts.ts | 30 --
src/test_case.ts | 119 ------
src/types.ts | 18 -
tests/integration/asserts_test.ts | 45 ---
tests/integration/hooks/after_all_test.ts | 71 ----
tests/integration/hooks/after_each_test.ts | 62 ----
tests/integration/hooks/before_all_test.ts | 63 ----
tests/integration/hooks/before_each_test.ts | 71 ----
tests/integration/mock_test.ts | 1 +
tests/unit/mod_test.ts | 76 ----
tests/unit/quick_start.ts | 33 --
tests/unit/test_case_test.ts | 24 --
26 files changed, 13 insertions(+), 1544 deletions(-)
delete mode 100644 deps.ts
delete mode 100644 example_tests/basic/1.ts
delete mode 100644 example_tests/basic/1_fail.ts
delete mode 100644 example_tests/basic/2.ts
delete mode 100644 example_tests/basic/3.ts
delete mode 100644 example_tests/basic/tests_fail.ts
delete mode 100644 example_tests/basic/tests_pass.ts
delete mode 100644 src/interfaces.ts
delete mode 100644 src/mock_builder.ts
delete mode 100644 src/mocks/server_request.ts
delete mode 100644 src/rhum_asserts.ts
delete mode 100644 src/test_case.ts
delete mode 100644 src/types.ts
delete mode 100644 tests/integration/asserts_test.ts
delete mode 100644 tests/integration/hooks/after_all_test.ts
delete mode 100644 tests/integration/hooks/after_each_test.ts
delete mode 100644 tests/integration/hooks/before_all_test.ts
delete mode 100644 tests/integration/hooks/before_each_test.ts
delete mode 100644 tests/unit/mod_test.ts
delete mode 100644 tests/unit/quick_start.ts
delete mode 100644 tests/unit/test_case_test.ts
diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
index aff77b1b..68d7de96 100644
--- a/.github/workflows/master.yml
+++ b/.github/workflows/master.yml
@@ -22,10 +22,10 @@ jobs:
uses: denolib/setup-deno@master
- name: Unit
- run: deno test --allow-env tests/unit
+ run: deno test tests/unit
- name: Integration
- run: deno test --allow-run --allow-env tests/integration
+ run: deno test tests/integration
linter:
# Only one OS is required since fmt is cross platform
@@ -35,10 +35,10 @@ jobs:
- uses: actions/checkout@v2
- name: Install Deno
- uses: denolib/setup-deno@master
+ uses: denoland/setup-deno@v1
- # - name: Lint
- # run: deno lint --unstable
+ - name: Lint
+ run: deno lint
- name: Formatter
run: deno fmt --check
diff --git a/README.md b/README.md
index d85f416e..7ea820a5 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Rhum
-A lightweight testing framework for Deno.
+A test double module to stub and mock your code
@@ -25,13 +25,10 @@
### Features
-- Descriptive naming for your tests
- Lightweight
- Zero 3rd party dependencies
- Simple and easy to use
- Asynchronous support
-- Still uses `Deno.test` under the hood
-- Skip functionality
- Mock requests
- Hooks
@@ -40,32 +37,11 @@
To add Rhum to your project, follow the quick start guide
[here](https://drash.land/rhum/#/#quickstart).
-### Why Use Rhum?
+## Contributing
-Rhum allows you to write tests in a very descriptive way -- from a code
-perspective or output perspective.
-
-Rhum is designed to aid your testing efforts -- providing many utilities as
-wrappers around Deno's existing `Deno.test`. Rhum is meant to improve the user
-experience when it comes to writing tests, such as:
-
-- Readability for test cases
-- Features that aren't available in Deno yet (hooks)
-
-Rhum takes concepts from the following:
-
-- Mocha — For how you
- write tests in Rhum, and the use of
- hooks
-- Baretest —
- Being minimalistic
-
-Rhum can be added directly into any project. All you need to do is import Rhum
-and you are ready to start writing tests or bring your existing tests under
-Rhum.
+Want to contribute? Follow the Contributing Guidelines
+[here](https://github.com/drashland/.github/blob/master/CONTRIBUTING.md).
----
+## License
-Want to contribute? Follow the Contributing Guidelines
-[here](https://github.com/drashland/.github/blob/master/CONTRIBUTING.md). All
-code is released under the [MIT License](./LICENSE).
+All code is released under the [MIT License](./LICENSE).
diff --git a/deps.ts b/deps.ts
deleted file mode 100644
index 715241eb..00000000
--- a/deps.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export { ServerRequest } from "https://deno.land/std@0.106.0/http/server.ts";
-
-export { BufReader } from "https://deno.land/std@0.106.0/io/bufio.ts";
-
-export * as StdAsserts from "https://deno.land/std@0.106.0/testing/asserts.ts";
-
-export * as colors from "https://deno.land/std@0.106.0/fmt/colors.ts";
diff --git a/egg.json b/egg.json
index 95699add..b3578378 100644
--- a/egg.json
+++ b/egg.json
@@ -1,13 +1,11 @@
{
"name": "rhum",
- "description": "A lightweight testing framework for Deno.",
+ "description": "A test double mdoule to stub and mock your code.",
"version": "1.1.11",
"stable": true,
"repository": "https://github.com/drashland/rhum",
"files": [
"./mod.ts",
- "./deps.ts",
- "./src/**/*",
"./README.md",
"./logo.svg",
"LICENSE"
diff --git a/example_tests/basic/1.ts b/example_tests/basic/1.ts
deleted file mode 100644
index eaae3d3e..00000000
--- a/example_tests/basic/1.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Rhum } from "../../mod.ts";
-
-Rhum.testPlan("test_plan_1", () => {
- Rhum.testSuite("test_suite_1a", () => {
- Rhum.testCase("test_case_1a1", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_1a2", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_1a3", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- });
-
- Rhum.testSuite("test_suite_1b", () => {
- Rhum.testCase("test_case_1b1", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_1b2", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_1b3", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- });
-});
-
-Rhum.run();
diff --git a/example_tests/basic/1_fail.ts b/example_tests/basic/1_fail.ts
deleted file mode 100644
index 5924602d..00000000
--- a/example_tests/basic/1_fail.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Rhum } from "../../mod.ts";
-
-Rhum.testPlan("test_plan_1", () => {
- Rhum.testSuite("test_suite_1a", () => {
- Rhum.testCase("test_case_1a1", () => {
- Rhum.asserts.assertEquals(true, false);
- });
- Rhum.testCase("test_case_1a2", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_1a3", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- });
-
- Rhum.testSuite("test_suite_1b", () => {
- Rhum.testCase("test_case_1b1", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_1b2", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_1b3", () => {
- Rhum.asserts.assertEquals(true, false);
- });
- });
-});
-
-Rhum.run();
diff --git a/example_tests/basic/2.ts b/example_tests/basic/2.ts
deleted file mode 100644
index c45c87fc..00000000
--- a/example_tests/basic/2.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { Rhum } from "../../mod.ts";
-
-Rhum.testPlan("test_plan_2", () => {
- Rhum.testSuite("test_suite_2a", () => {
- Rhum.testCase("test_case_2a1", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_2a2", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_2a3", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- });
-
- Rhum.testSuite("test_suite_2b", () => {
- Rhum.testCase("test_case_2b1", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_2b2", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- });
-
- Rhum.testSuite("test_suite_2c", () => {
- Rhum.testCase("test_case_2c1", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_2c2", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_2c3", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- });
-
- Rhum.testSuite("test_suite_2d", () => {
- Rhum.testCase("test_case_2d1", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_2d2", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- });
-});
-
-Rhum.run();
diff --git a/example_tests/basic/3.ts b/example_tests/basic/3.ts
deleted file mode 100644
index 40b52b0c..00000000
--- a/example_tests/basic/3.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Rhum } from "../../mod.ts";
-
-Rhum.testPlan("test_plan_3", () => {
- Rhum.testSuite("test_suite_3a", () => {
- Rhum.testCase("test_case_3a1", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_3a2", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- });
-
- Rhum.testSuite("test_suite_3b", () => {
- Rhum.testCase("test_case_3b1", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_3b2", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- });
-
- Rhum.testSuite("test_suite_3c", () => {
- Rhum.testCase("test_case_3c1", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("test_case_3c2", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- });
-});
-
-Rhum.run();
diff --git a/example_tests/basic/tests_fail.ts b/example_tests/basic/tests_fail.ts
deleted file mode 100644
index 978ca4eb..00000000
--- a/example_tests/basic/tests_fail.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import "./1_fail.ts";
-import "./2.ts";
-import "./3.ts";
diff --git a/example_tests/basic/tests_pass.ts b/example_tests/basic/tests_pass.ts
deleted file mode 100644
index a48c96b7..00000000
--- a/example_tests/basic/tests_pass.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import "./1.ts";
-import "./2.ts";
-import "./3.ts";
diff --git a/mod.ts b/mod.ts
index 1ef6de0b..e0e7eb89 100644
--- a/mod.ts
+++ b/mod.ts
@@ -1,253 +1,13 @@
-import { assertions, asserts } from "./src/rhum_asserts.ts";
-import { MockServerRequestFn } from "./src/mocks/server_request.ts";
-import { TestCase } from "./src/test_case.ts";
-import type { ITestPlan, RhumMocks } from "./src/interfaces.ts";
import type { Constructor, Stubbed } from "./src/types.ts";
import { MockBuilder } from "./src/mock_builder.ts";
export type { Constructor, Stubbed } from "./src/types.ts";
export { MockBuilder } from "./src/mock_builder.ts";
-/**
- * Deno's test runner outputs "test ", which has a length of 5. This module
- * erases the "test " string by backspacing the test plan line and test suite
- * line by that number. For safety, it substracts twice that number. This is
- * how we get the number 10 here.
- */
-const extraChars = 10;
-
-/**
- * This testing framework allows the following syntax:
- *
- * import { Rhum } from "/path/to/rhum/mod.ts";
- *
- * Rhum.testPlan("test_plan_1", () => {
- *
- * Rhum.testSuite("test_suite_1a", () => {
- * Rhum.testCase("test_case_1a1", () => {
- * Rhum.asserts.assertEquals(true, true);
- * });
- * Rhum.testCase("test_case_1a2", () => {
- * Rhum.asserts.assertEquals(true, true);
- * });
- * Rhum.testCase("test_case_1a3", () => {
- * Rhum.asserts.assertEquals(true, true);
- * });
- * });
- *
- * Rhum.testSuite("test_suite_1b", () => {
- * Rhum.testCase("test_case_1b1", () => {
- * Rhum.asserts.assertEquals(true, true);
- * });
- * Rhum.testCase("test_case_1b2", () => {
- * Rhum.asserts.assertEquals(true, true);
- * });
- * Rhum.testCase("test_case_1b3", () => {
- * Rhum.asserts.assertEquals(true, true);
- * });
- * });
- *
- * });
- */
export class RhumRunner {
- /**
- * The asserts module from https://deno.land/std/testing, but attached to Rhum
- * for accessibility.
- *
- * Rhum.asserts.assertEquals(true, true); // pass
- * Rhum.asserts.assertEquals(true, false); // fail
- */
- // deno-lint-ignore ban-types Reason for this is, deno lint no longer allows `Function` and instead needs us to be explicit: `() => void`, but because we couldn't use that to type the properties (we would just be copying Deno's interfaces word for word), we have to deal with `Function
- public asserts: { [key in assertions]: Function } = asserts;
-
- public mocks: RhumMocks;
-
- protected passed_in_test_plan = "";
-
- protected passed_in_test_suite = "";
-
- protected test_plan_in_progress = "";
-
- protected test_suite_in_progress = "";
-
- protected plan: ITestPlan = { suites: {} };
-
- // FILE MARKER - METHODS - CONSTRUCTOR ///////////////////////////////////////
-
- /**
- * Construct an object of this class.
- */
- constructor() {
- this.mocks = { ServerRequest: MockServerRequestFn };
- }
// FILE MARKER - METHODS - PUBLIC ////////////////////////////////////////////
- /**
- * Used to define a hook that will execute before each test suite or test
- * case. If this is used inside of a test plan, then it will execute before
- * each test suite. If this is used inside of a test suite, then it will
- * execute before each test case.
- *
- * @param cb - The callback to invoke. Would contain the required logic you
- * need to do what you want, before each test suite or case.
- *
- * Rhum.testPlan("My Plan", () => {
- * Rhum.beforeEach(() => {
- * // Runs before each test suite in this test plan
- * });
- * Rhum.testSuite("My Suite 1", () => {
- * Rhum.beforeEach(() => {
- * // Runs before each test case in this test suite
- * });
- * Rhum.testCase("My Test Case 1", () => {
- * ...
- * });
- * });
- * });
- */
- public beforeEach(cb: () => void): void {
- // Check if the hook is for test cases inside of a suite
- if (this.passed_in_test_plan && this.passed_in_test_suite) {
- // is a before each inside a suite for every test case
- this.plan.suites![this.passed_in_test_suite].before_each_case_hook = cb;
- } else if (this.passed_in_test_plan && !this.passed_in_test_suite) {
- // before each hooks for the suites
- this.plan.before_each_suite_hook = cb;
- }
- }
-
- /**
- * Used to define a hook that will execute after each test suite or test case.
- * If this is used inside of a test plan, then it will execute after each test
- * suite. If this is used inside of a test suite, then it will execute after
- * each test case.
- *
- * @param cb - The callback to invoke. Would contain the required logic you
- * need to do what you want, after each test suite or case.
- *
- * Rhum.testPlan("My Plan", () => {
- * Rhum.afterEach(() => {
- * // Runs after each test suite in this test plan
- * });
- * Rhum.testSuite("My Suite 1", () => {
- * Rhum.afterEach(() => {
- * // Runs after each test case in this test suite
- * });
- * Rhum.testCase("My Test Case 1", () => {
- * ...
- * });
- * });
- * });
- */
- public afterEach(cb: () => void): void {
- // Check if the hook is for test cases inside of a suite
- if (this.passed_in_test_plan && this.passed_in_test_suite) {
- // is a after each inside a suite for every test case
- this.plan.suites![this.passed_in_test_suite].after_each_case_hook = cb;
- } else if (this.passed_in_test_plan && !this.passed_in_test_suite) {
- // after each hooks for the suites
- this.plan.after_each_suite_hook = cb;
- }
- }
-
- /**
- * Used to define a hook that will execute after all test suites or test
- * cases. If this is used inside of a test plan, then it will execute after
- * all test suites. If this is used inside of a test suite, then it will
- * execute after all test cases.
- *
- * @param cb - The callback to invoke. Would contain the required logic you
- * need to do what you want, after all test suites or cases.
- *
- * Rhum.testPlan("My Plan", () => {
- * Rhum.afterAll(() => {
- * // Runs once after all test suites in this test plan
- * });
- * Rhum.testSuite("My Suite 1", () => {
- * Rhum.afterAll(() => {
- * // Runs once after all test cases in this test suite
- * });
- * Rhum.testCase("My Test Case 1", () => {
- * ...
- * });
- * });
- * });
- */
- public afterAll(cb: () => void): void {
- // Check if the hook is for test cases inside of a suite
- if (this.passed_in_test_plan && this.passed_in_test_suite) {
- // is a before all inside a suite for every test case
- this.plan.suites![this.passed_in_test_suite].after_all_case_hook = cb;
- } else if (this.passed_in_test_plan && !this.passed_in_test_suite) {
- // before all hooks for the suites
- this.plan.after_all_suite_hook = cb;
- }
- }
-
- /**
- * Used to define a hook that will execute before all test suites or test
- * cases. If this is used inside of a test plan, then it will execute before
- * all test suites. If this is used inside of a test suite, then it will
- * execute before all test cases.
- *
- * @param cb - The callback to invoke. Would contain the required logic you
- * need to do what you want, before all test suites or cases.
- *
- * Rhum.testPlan("My Plan", () => {
- * Rhum.beforeAll(() => {
- * // Runs once before all test suites in this test plan
- * });
- * Rhum.testSuite("My Suite 1", () => {
- * Rhum.beforeAll(() => {
- * // Runs once before all test cases in this test suite
- * });
- * Rhum.testCase("My Test Case 1", () => {
- * ...
- * });
- * });
- * });
- */
- public beforeAll(cb: () => void): void {
- // Check if the hook is for test cases inside of a suite
- if (this.passed_in_test_plan && this.passed_in_test_suite) {
- // is a before all inside a suite for every test case
- this.plan.suites![this.passed_in_test_suite].before_all_case_hook = cb;
- } else if (this.passed_in_test_plan && !this.passed_in_test_suite) {
- // before all hooks for the suites
- this.plan.before_all_suite_hook = cb;
- }
- }
-
- // public only(cb: Function): void {
- // // Do something
- // }
-
- /**
- * Allows a test plan, suite, or case to be skipped when the tests run.
- *
- * Rhum.testPlan("My Plan", () => {
- * Rhum.skip("My Suite 1", () => { // will not run this block
- * Rhum.testCase("My Test Case In Suite 1", () => {
- * ...
- * });
- * });
- * Rhum.testSuite("My Suite 2", () => {
- * Rhum.testCase("My Test Case In Suite 2", () => {
- * ...
- * });
- * Rhum.skip("My Other Test Case In Suite 2", () => { // will not run this block
- * ...
- * });
- * });
- * });
- */
- public skip(name: string, cb: () => void): void {
- // TODO(ebebbington|crookse) Maybe we could still call run, but pass in {
- // ignore: true } which the Deno.Test will use? just so it displays ignored
- // in the console
- }
-
/**
* Stub a member of an object.
*
@@ -305,156 +65,6 @@ export class RhumRunner {
public mock(constructorFn: Constructor): MockBuilder {
return new MockBuilder(constructorFn);
}
-
- /**
- * A test case is grouped by a test suite and it is what makes the assertions
- * - it is the test. You can define multiple test cases under a test suite.
- * Test cases can also be asynchronous. Test cases can only be defined inside
- * of a test suite.
- *
- * @param name - The name of the test case.
- * @param testFn - The test to execute.
- *
- * Rhum.testPlan("My Plan", () => {
- * Rhum.testSuite("My Suite 1", () => {
- * Rhum.testCase("My Test Case 1", () => {
- * Rhum.assert.assertEquals(something, true);
- * });
- * Rhum.testCase("My Test Case 2", () => {
- * Rhum.assert.assertEquals(something, false);
- * });
- * });
- * });
- */
- public testCase(name: string, testFn: () => void): void {
- this.plan.suites[this.passed_in_test_suite].cases!.push({
- name,
- new_name: this.formatTestCaseName(name),
- testFn,
- });
- }
-
- /**
- * Groups up test suites to describe a test plan. Usually, a test plan is per
- * file and contains the tests suites and test cases for a single file. Test
- * plans are required in order to define a test suite with test cases.
- *
- * @param name - The name of the test plan.
- * @param testSuites - The test suites to execute.
- *
- * Rhum.testPlan("My Plan", () => {
- * ...
- * });
- */
- public testPlan(name: string, testSuites: () => void): void {
- this.passed_in_test_suite = ""; // New plan
- this.passed_in_test_plan = name;
- testSuites();
- }
-
- /**
- * A test suite usually describes a method or property name and groups up all
- * test cases for that method or property. You can define multiple test suites
- * under a test plan. Test suites can only be defined inside of a test plan.
- *
- * @param name - The name of the test suite.
- * @param testCases - The test cases to execute.
- *
- * Rhum.testPlan("My Plan", () => {
- * Rhum.testSuite("My Suite 1", () => {
- * ...
- * });
- * Rhum.testSuite("My Suite 2", () => {
- * ...
- * });
- * });
- */
- public testSuite(name: string, testCases: () => void): void {
- this.passed_in_test_suite = name;
- this.plan.suites![name] = { cases: [] };
- testCases();
- }
-
- /**
- * Run the test plan.
- *
- * Rhum.testPlan("My Plan", () => {
- * ...
- * });
- *
- * Rhum.run();
- */
- public run(): void {
- const tc = new TestCase(this.plan);
- tc.run();
- this.deconstruct();
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // FILE MARKER - METHODS - PROTECTED /////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
-
- /**
- * Figure out the name of the test case for output purposes.
- *
- * @param name - The name of the test case.
- *
- * Returns the new test name for outputting purposes.
- */
- protected formatTestCaseName(name: string): string {
- let newName: string;
- // (ebebbington) Unfortunately, due to the CI not correctly displaying output
- // (it is all over the place and just completely unreadable as
- // it doesn't play well with our control characters), we need to
- // display the test output differently, based on if the tests are
- // being ran inside a CI or not. Nothing will change for the current
- // way of doing things, but if the tests are being ran inside a CI,
- // the format would be:
- // test | | ... ok (2ms)
- // test | | ... ok (2ms)
- // Even if plans and/or suites are the same. I believe this the best
- // way we can display the output
- if (Deno.env.get("CI") === "true") {
- newName =
- `${this.passed_in_test_plan} | ${this.passed_in_test_suite} | ${name}`;
- return newName;
- }
-
- if (this.test_plan_in_progress != this.passed_in_test_plan) {
- this.test_plan_in_progress = this.passed_in_test_plan;
- this.test_suite_in_progress = this.passed_in_test_suite;
- newName =
- `${
- "\u0008".repeat(name.length + extraChars)
- } ${this.passed_in_test_suite}` +
- `\n ${name}`;
- } else {
- if (this.test_suite_in_progress != this.passed_in_test_suite) {
- this.test_suite_in_progress = this.passed_in_test_suite;
- newName = `${"\u0008".repeat(name.length + extraChars)}` +
- ` ${this.passed_in_test_suite}` +
- `${" ".repeat(name.length + extraChars)}` +
- `\n ${name}`;
- } else {
- newName = `${"\u0008".repeat(name.length + extraChars)}` +
- ` ${name}`;
- }
- }
-
- return newName;
- }
-
- /**
- * 'Empty' this object. After calling this, Rhum should be ready for another
- * test plan.
- */
- protected deconstruct(): void {
- this.passed_in_test_suite = "";
- this.passed_in_test_plan = "";
- this.test_plan_in_progress = "";
- this.test_suite_in_progress = "";
- this.plan = { suites: {} };
- }
}
/**
diff --git a/src/interfaces.ts b/src/interfaces.ts
deleted file mode 100644
index f887fac9..00000000
--- a/src/interfaces.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import type { MockServerRequestFn } from "./mocks/server_request.ts";
-
-/**
- * @remarks
- * suites
- * An object of objects matching the ITestSuite interface.
- *
- * after_all_suite_hook?
- * A callback function to execute after all test suites.
- *
- * after_each_suite_hook?
- * A callback function to execute after each test suite.
- *
- * before_all_suite_hook?
- * A callback function to execute before all test suites.
- *
- * before_each_suite_hook?
- * A callback function to execute before each test suite.
- *
- * Example below ...
- *
- * {
- * suites: {
- * "My Suite": {
- * cases: [
- * {
- * name: "My Case",
- * new_name: this.formatTestCaseName(name),
- * testFn: Function
- * },
- * ...
- * ],
- * after_all_case_hook: Function,
- * after_each_case_hook: Function,
- * before_all_case_hook: Function,
- * before_each_case_hook: Function
- * },
- * ... // More suites allowed
- * },
- * after_all_suite_hook: Function;
- * after_each_suite_hook: Function;
- * before_all_suite_hook: Function;
- * before_each_suite_hook: Function;
- * }
- *
- * ... or ...
- *
- * {
- * suites: {
- * run(): {
- * cases: [Array],
- * after_all_case_hook: [Function],
- * before_all_case_hook: [Function],
- * before_each_case_hook: [Function],
- * after_each_case_hook: [Function]
- * },
- * close(): {
- * cases: [Array],
- * after_all_case_hook: [Function],
- * before_all_case_hook: [Function],
- * before_each_case_hook: [Function],
- * after_each_case_hook: [Function]
- * }
- * },
- * before_each_suite_hook: [Function],
- * after_each_suite_hook: [Function],
- * after_all_suite_hook: [Function],
- * before_all_suite_hook: [Function]
- * }
- */
-export interface ITestPlan {
- suites: {
- [key: string]: ITestSuite; // "key" is the suite name
- };
- after_all_suite_hook?: () => void;
- after_each_suite_hook?: () => void;
- before_all_suite_hook?: () => void;
- before_each_suite_hook?: () => void;
-}
-
-/**
- * cases?
- * An array of objects matching the ITestCase interface.
- *
- * after_all_case_hook?
- * A callback function to execute after all test cases.
- *
- * after_each_case_hook?
- * A callback function to execute after each test case.
- *
- * before_all_case_hook?
- * A callback function to execute before all test cases.
- *
- * before_each_case_hook?
- * A callback function to execute before each test case.
- */
-export interface ITestSuite {
- cases?: ITestCase[];
- after_all_case_hook?: () => void;
- after_each_case_hook?: () => void;
- before_all_case_hook?: () => void;
- before_each_case_hook?: () => void;
-}
-
-/**
- * name
- * The name of the test case.
- *
- * new_name
- * The new name of the test. This is strictly for outputting purposes.
- * Deno's test runner outputs "test name of test" and we want to
- * overwrite that text. This new_name string helps us do that. See
- * formatTestCaseName() in mod.ts for more information.
- *
- * testFn
- * The test function. Ultimately, this gets passed as the second
- * argument of Deno.test().
- */
-export interface ITestCase {
- name: string;
- new_name: string;
- testFn: () => void;
-}
-
-/**
- * ServerRequest
- * Type for the ServerRequest on the `Rhum.mocks` property
- */
-export interface RhumMocks {
- ServerRequest: typeof MockServerRequestFn;
-}
diff --git a/src/mock_builder.ts b/src/mock_builder.ts
deleted file mode 100644
index 3fa4a491..00000000
--- a/src/mock_builder.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import type { Constructor, Mocked } from "./types.ts";
-
-export class MockBuilder {
- /**
- * Properties of the class that is passed in
- */
- protected properties: string[] = [];
-
- /**
- * Functions of the class passed in
- */
- protected functions: string[] = [];
-
- /**
- * The class object passed into the constructor
- */
- protected constructor_fn: Constructor;
-
- /**
- * A list of arguments the class constructor takes
- */
- protected constructor_args: unknown[] = [];
-
- //////////////////////////////////////////////////////////////////////////////
- // FILE MARKER - CONSTRUCTOR /////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
-
- /**
- * Construct an object of this class.
- *
- * @param constructorFn - The object's constructor function to instantiate.
- */
- constructor(constructorFn: Constructor) {
- this.constructor_fn = constructorFn;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // FILE MARKER - METHODS - PUBLIC ////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
-
- /**
- * Create the mock object.
- *
- * @returns A mocked object.
- */
- public create(): Mocked {
- // deno-lint-ignore no-explicit-any
- const mock: Mocked = {
- calls: {},
- is_mock: true,
- };
- const original = new this.constructor_fn(...this.constructor_args);
-
- // Attach all of the original's properties to the mock
- this.getAllProperties(original).forEach((property: string) => {
- const desc = Object.getOwnPropertyDescriptor(original, property);
- mock[property] = desc!.value;
- });
-
- // Attach all of the original's functions to the mock
- this.getAllFunctions(original).forEach((method: string) => {
- const nativeMethods = [
- "__defineGetter__",
- "__defineSetter__",
- "__lookupGetter__",
- "__lookupSetter__",
- "constructor",
- "hasOwnProperty",
- "isPrototypeOf",
- "propertyIsEnumerable",
- "toLocaleString",
- "toString",
- "valueOf",
- ];
-
- if (nativeMethods.indexOf(method) == -1) {
- if (!mock.calls[method]) {
- mock.calls[method] = 0;
- }
- mock[method] = function () {
- mock.calls[method]++;
- return (original[method as keyof T] as unknown as (
- ...params: unknown[]
- ) => unknown)();
- };
- } else {
- // copy nativeMethod directly without mocking
- mock[method] = original[method as keyof T];
- }
- });
-
- return mock;
- }
-
- /**
- * Before constructing the mock object, track any constructur function args
- * that need to be passed in when constructing the mock object.
- *
- * @param args - A rest parameter of arguments that will get passed in to
- * the constructor function of the class being mocked.
- *
- * @returns `this` so that methods in this class can be chained.
- */
- public withConstructorArgs(...args: unknown[]): this {
- this.constructor_args = args;
- return this;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // FILE MARKER - METHODS - PROTECTED /////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
-
- /**
- * Get all properties--public, protected, private--from the object that will
- * be mocked.
- *
- * @param obj - The object that will be mocked.
- *
- * @returns An array of the object's properties.
- */
- protected getAllProperties(obj: T): string[] {
- let functions: string[] = [];
- let clone = obj;
- do {
- functions = functions.concat(Object.getOwnPropertyNames(clone));
- } while ((clone = Object.getPrototypeOf(clone)));
-
- return functions.sort().filter(
- function (e: string, i: number, arr: unknown[]) {
- if (
- e != arr[i + 1] && typeof obj[e as keyof T] != "function"
- ) {
- return true;
- }
- },
- );
- }
-
- /**
- * Get all functions--public, protected, private--from the object that will be
- * mocked.
- *
- * @param obj - The object that will be mocked.
- *
- * @returns An array of the object's functions.
- */
- protected getAllFunctions(obj: T): string[] {
- let functions: string[] = [];
- let clone = obj;
- do {
- functions = functions.concat(Object.getOwnPropertyNames(clone));
- } while ((clone = Object.getPrototypeOf(clone)));
-
- return functions.sort().filter(
- function (e: string, i: number, arr: unknown[]) {
- if (
- e != arr[i + 1] && typeof obj[e as keyof T] == "function"
- ) {
- return true;
- }
- },
- );
- }
-}
diff --git a/src/mocks/server_request.ts b/src/mocks/server_request.ts
deleted file mode 100644
index f204b8c8..00000000
--- a/src/mocks/server_request.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { BufReader, ServerRequest } from "../../deps.ts";
-
-/**
- * Contains options for a server request
- *
- * @remarks
- * headers: {[key: string]: string}
- *
- * The headers for the request
- *
- * body?: Deno.Buffer
- *
- * The body of the request before sending
- */
-export interface MockServerRequestOptions {
- headers: { [key: string]: string };
- body?: Deno.Buffer;
-}
-
-// TODO remove lint ignore when extended and add tsdoc block
-// deno-lint-ignore no-empty-interface, eslint-ignore-next-line no-empty-interface
-export interface MockServerRequest extends ServerRequest {
-}
-
-/**
- * Constructs a server request object
- *
- * @example
- * ```ts
- * const request = MockServerRequestFn("/", "get", { ... });
- * ```
- *
- * @param url - Url to make the request to
- * @param method - The method of the request
- * @param options - HTTP request options, such as headers and body
- */
-export const MockServerRequestFn = function (
- url = "/",
- method = "get",
- options?: MockServerRequestOptions,
-): MockServerRequest {
- const request: MockServerRequest = new ServerRequest();
- request.url = url;
- request.method = method;
- request.headers = new Headers();
- if (options) {
- if (options.headers) {
- for (const key in options.headers) {
- request.headers.set(key, options.headers[key]);
- }
- }
- if (options.body) {
- request.headers.set("Content-Length", options.body.length.toString());
- request.r = new BufReader(options.body);
- }
- }
-
- return request;
-};
diff --git a/src/rhum_asserts.ts b/src/rhum_asserts.ts
deleted file mode 100644
index 981caac8..00000000
--- a/src/rhum_asserts.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { StdAsserts } from "../deps.ts";
-
-// Saving this so i don't have to type it out for the 10th time
-// export const asserts = {
-// AssertionError: StdAsserts.AssertionError,
-// _format: StdAsserts._format,
-// assert: StdAsserts.assert,
-// assertArrayIncludes: StdAsserts.assertArrayIncludes,
-// assertEquals: StdAsserts.assertEquals,
-// assertNotEquals: StdAsserts.assertNotEquals,
-// assertNotMatch: StdAsserts.assertNotMatch,
-// assertNotStrictEquals: StdAsserts.assertNotStrictEquals,
-// assertStrictEquals: StdAsserts.assertStrictEquals,
-// assertStringIncludes: StdAsserts.assertStringIncludes,
-// assertMatch: StdAsserts.assertMatch,
-// assertThrows: StdAsserts.assertThrows,
-// assertThrowsAsync: StdAsserts.assertThrowsAsync,
-// equal: StdAsserts.equal,
-// fail: StdAsserts.fail,
-// unimplemented: StdAsserts.unimplemented,
-// unreachable: StdAsserts.unreachable
-// };
-
-export const asserts = {
- ...StdAsserts,
- assertArrayContains: StdAsserts.assertArrayIncludes,
- assertStringContains: StdAsserts.assertStringIncludes,
-};
-
-export type assertions = keyof typeof asserts;
diff --git a/src/test_case.ts b/src/test_case.ts
deleted file mode 100644
index c02193bf..00000000
--- a/src/test_case.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-const encoder = new TextEncoder();
-import type { ITestCase, ITestPlan } from "./interfaces.ts";
-
-/**
- * A class to help create uniform test case objects.
- */
-export class TestCase {
- /**
- * The whole test plan for a given test
- */
- protected plan: ITestPlan;
-
- /**
- * @param plan - The test plan for a given test
- */
- constructor(plan: ITestPlan) {
- this.plan = plan;
- }
-
- /**
- * Runs the test plan and each test and hook
- */
- public async run() {
- // deno-lint-ignore no-prototype-builtins, eslint-ignore-next-line no-prototype-builtins
- if (this.plan.hasOwnProperty("suites") === false) {
- return;
- }
-
- // Track the execution of hooks
- let executedBeforeAllSuiteHook = false;
- let executedAfterAllSuiteHook = false;
-
- Object.keys(this.plan.suites).forEach((suiteName, suiteIndex) => {
- // Track the execution of hooks
- let executedBeforeEachSuiteHook = false;
- let executedAfterEachSuiteHook = false;
- let executedBeforeAllCaseHook = false;
- let executedAfterAllCaseHook = false;
-
- // Run cases
- this.plan!.suites[suiteName].cases!.forEach(
- async (c: ITestCase, caseIndex) => {
- const isLastCase =
- (this.plan!.suites[suiteName].cases!.length - 1) == caseIndex;
- const isLastSuite =
- (Object.keys(this.plan!.suites).length - 1) == suiteIndex;
- // Run the case - required to run like this because the
- // hooks need to be ran inside the Deno.test call. Deno.test seems to queue
- // the tests, meaning all hooks are ran, and **then** the tests are ran
- const hookAttachedTestFn = async () => {
- if (
- this.plan.before_all_suite_hook && !executedBeforeAllSuiteHook
- ) {
- await this.plan.before_all_suite_hook();
- executedBeforeAllSuiteHook = true;
- }
- if (
- this.plan.before_each_suite_hook && !executedBeforeEachSuiteHook
- ) {
- await this.plan.before_each_suite_hook();
- executedBeforeEachSuiteHook = true;
- }
- if (
- this.plan.suites[suiteName].before_all_case_hook &&
- !executedBeforeAllCaseHook
- ) {
- await this.plan.suites[suiteName].before_all_case_hook!();
- executedBeforeAllCaseHook = true;
- }
- if (this.plan.suites[suiteName].before_each_case_hook) {
- await this.plan.suites[suiteName].before_each_case_hook!();
- }
-
- await c.testFn();
-
- if (this.plan.suites[suiteName].after_each_case_hook) {
- await this.plan.suites[suiteName].after_each_case_hook!();
- }
- if (
- this.plan.suites[suiteName].after_all_case_hook &&
- !executedAfterAllCaseHook && isLastCase
- ) {
- await this.plan.suites[suiteName].after_all_case_hook!();
- executedAfterAllCaseHook = true;
- }
- if (
- this.plan.after_each_suite_hook && !executedAfterEachSuiteHook
- ) {
- await this.plan.after_each_suite_hook();
- executedAfterEachSuiteHook = true;
- }
- if (
- this.plan.after_all_suite_hook && !executedAfterAllSuiteHook &&
- isLastSuite
- ) {
- await this.plan.after_all_suite_hook();
- executedAfterAllSuiteHook = true;
- }
- };
- // (ebebbington) To stop the output of test running being horrible
- // in the CI, we will only display the new name which should be
- // "plan | suite " case", as opposed to the "super saiyan"
- // version. This name is generated differently inside `formatTestCaseName`
- // based on if the tests are being ran inside a CI job
- if (Deno.env.get("CI") === "true") {
- await Deno.test(c.new_name, async () => {
- await hookAttachedTestFn();
- });
- } else {
- await Deno.test(c.new_name, async () => {
- // Deno.stdout.writeSync(encoder.encode(c.new_name));
- await hookAttachedTestFn();
- });
- }
- },
- );
- });
- }
-}
diff --git a/src/types.ts b/src/types.ts
deleted file mode 100644
index 4c6125a6..00000000
--- a/src/types.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-export type TConstructorFunction = {
- new (...args: unknown[]): T;
- [key: string]: unknown;
-};
-
-// deno-lint-ignore no-explicit-any
-export type Constructor = new (...args: any[]) => T;
-
-export type Mocked = T & {
- calls: { [k in keyof T]: T[k] extends () => void ? number : never };
- is_mock: true;
-};
-
-export type Stubbed = T & {
- calls: { [k in keyof T]?: T[k] extends () => void ? number : never };
- stub: (p: string, v: unknown) => void;
- is_stubbed: true;
-};
diff --git a/tests/integration/asserts_test.ts b/tests/integration/asserts_test.ts
deleted file mode 100644
index d22e922a..00000000
--- a/tests/integration/asserts_test.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Rhum } from "../../mod.ts";
-
-Rhum.testPlan("asserts_test.ts", () => {
- Rhum.testSuite("Deno std asserts", () => {
- Rhum.testCase("asserts", () => {
- Rhum.asserts.assert(true);
- });
- Rhum.testCase("assertEquals", () => {
- Rhum.asserts.assertEquals(true, true);
- });
- Rhum.testCase("assertNotEquals", () => {
- Rhum.asserts.assertNotEquals(true, false);
- Rhum.asserts.assertNotEquals("1", 1);
- });
- Rhum.testCase("assertStrictEquals", () => {
- Rhum.asserts.assertStrictEquals(true, true);
- const a = {};
- const b = a;
- Rhum.asserts.assertStrictEquals(a, b);
- });
- Rhum.testCase("assertStringContains", () => {
- Rhum.asserts.assertStringContains("Test hello", "hello");
- });
- Rhum.testCase("assertMatch", () => {
- Rhum.asserts.assertMatch("Test hello", /hello/g);
- });
- Rhum.testCase("assertArrayContains", () => {
- Rhum.asserts.assertArrayContains(["t", "e", "s", "t"], ["t", "e"]);
- });
- Rhum.testCase("assertThrows", () => {
- Rhum.asserts.assertThrows(() => {
- throw new Error("test");
- });
- });
- Rhum.testCase("assertThrowsAsync", () => {
- Rhum.asserts.assertThrowsAsync(() => {
- return Rhum.asserts.assertThrowsAsync(() => {
- return Promise.reject("Panic!");
- });
- });
- });
- });
-});
-
-Rhum.run();
diff --git a/tests/integration/hooks/after_all_test.ts b/tests/integration/hooks/after_all_test.ts
deleted file mode 100644
index 79a19289..00000000
--- a/tests/integration/hooks/after_all_test.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Purpose of this test is to ensure a test using before_all succeeds
- */
-
-import { Rhum } from "../../../mod.ts";
-
-let suite_val = 0;
-let case_val = 0;
-let async_case_val = 0;
-Rhum.testPlan("after_all_test.ts", () => {
- Rhum.afterAll(() => {
- Rhum.asserts.assertEquals(suite_val, 3);
- });
- // Run the first test suite
- Rhum.testSuite("test suite 1", () => {
- Rhum.afterAll(() => {
- Rhum.asserts.assertEquals(case_val, 2);
- case_val = 3;
- });
- Rhum.testCase("hooks properly set suite_val and case_val", () => {
- // Asserting that the suite val should not have been changed by the hook
- Rhum.asserts.assertEquals(suite_val, 0);
- suite_val = 1;
- // Asserting that the case val should not have been changed by the hook
- Rhum.asserts.assertEquals(case_val, 0);
- case_val = 1;
- });
- Rhum.testCase("hooks properly set case_val", () => {
- // Asserting that the suite val should not have been changed by the hook
- Rhum.asserts.assertEquals(suite_val, 1);
- suite_val = 2;
- // Asserting that the case val should not have been changed by the hook
- Rhum.asserts.assertEquals(case_val, 1);
- case_val = 2;
- });
- });
-
- // Run the second test suite
- Rhum.testSuite("test suite 2", () => {
- Rhum.afterAll(() => {
- Rhum.asserts.assertEquals(case_val, 4);
- });
- Rhum.testCase("hooks properly set suite_val and case_val", async () => {
- // Asserting that the suite val should not have been changed by the hook
- Rhum.asserts.assertEquals(suite_val, 2);
- suite_val = 3;
- // Asserting that the case val has been changed by the hook
- Rhum.asserts.assertEquals(case_val, 3);
- case_val = 4;
- });
- });
- Rhum.testSuite("test suite 3", () => {
- Rhum.afterAll(async () => {
- Rhum.asserts.assertEquals(async_case_val, 1);
- await new Promise((resolve) => {
- setTimeout(() => resolve((async_case_val = 2)), 1000);
- });
- });
- Rhum.testCase("Async afterAll hook has no effect before case", () => {
- Rhum.asserts.assertEquals(async_case_val, 0);
- async_case_val = 1;
- });
- });
- Rhum.testSuite("test suite 4", () => {
- Rhum.testCase("Async afterAll hook has effect after case", () => {
- Rhum.asserts.assertEquals(async_case_val, 2);
- });
- });
-});
-
-Rhum.run();
diff --git a/tests/integration/hooks/after_each_test.ts b/tests/integration/hooks/after_each_test.ts
deleted file mode 100644
index 9e935256..00000000
--- a/tests/integration/hooks/after_each_test.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * Purpose of this test is to ensure a test using before_each succeeds
- */
-
-import { Rhum } from "../../../mod.ts";
-
-let suite_val = "Ed";
-let case_val = 22;
-
-Rhum.testPlan("after_each_test.ts", () => {
- Rhum.afterEach(() => {
- suite_val = "Eric";
- });
- // Run the first test suite
- Rhum.testSuite("test suite 1", () => {
- Rhum.afterEach(() => {
- case_val = 2;
- });
- Rhum.testCase("hooks properly set suite_val and case_val", () => {
- // Asserting that the suite val should not have been changed by the hook (yet)
- Rhum.asserts.assertEquals(suite_val, "Ed");
- // Revert it back for the next test that uses it
- suite_val = "Ed";
- // Assert the value is kept the same (hook hasn't ran yet)
- Rhum.asserts.assertEquals(case_val, 22);
- });
- Rhum.testCase("Returns false", () => {
- // Assert the value has changes as the hook should have ran
- Rhum.asserts.assertEquals(case_val, 2);
- });
- });
-
- // Run the second test suite
- Rhum.testSuite("test suite 2", () => {
- Rhum.afterEach(() => {
- case_val = 0;
- });
- Rhum.testCase("hooks properly set suite_val and case_val", async () => {
- // Asserting the hook should have replaced the name
- Rhum.asserts.assertEquals(suite_val, "Eric");
- suite_val = "Ed";
- // Should be kept the same as the after each hook should have updated the value
- Rhum.asserts.assertEquals(case_val, 2);
- });
- });
- Rhum.testSuite("test suite 3", () => {
- let async_case_val = 5;
- Rhum.afterEach(async () => {
- await new Promise((resolve) => {
- setTimeout(() => resolve((async_case_val = 15)), 1000);
- });
- });
- Rhum.testCase("async afterEach hook has no effect before case", () => {
- Rhum.asserts.assertEquals(async_case_val, 5);
- });
- Rhum.testCase("async afterEach has effect after case", () => {
- Rhum.asserts.assertEquals(async_case_val, 15);
- });
- });
-});
-
-Rhum.run();
diff --git a/tests/integration/hooks/before_all_test.ts b/tests/integration/hooks/before_all_test.ts
deleted file mode 100644
index 6ae22809..00000000
--- a/tests/integration/hooks/before_all_test.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Purpose of this test is to ensure a test using before_each succeeds
- */
-
-import { Rhum } from "../../../mod.ts";
-
-let suite_val = "Ed";
-let case_val = 22;
-
-Rhum.testPlan("before_all_test.ts", () => {
- Rhum.beforeAll(() => {
- suite_val = "Eric";
- });
-
- // Run the first test suite
- Rhum.testSuite("test suite 1", () => {
- Rhum.beforeAll(() => {
- case_val = 2;
- });
-
- Rhum.testCase("hooks properly set suite_val and case_val", () => {
- // Asserting the value has changed
- Rhum.asserts.assertEquals(suite_val, "Eric");
- // Revert it back for the next test that uses it
- suite_val = "Ed";
- // Assert the value has changed from 22 to 2
- Rhum.asserts.assertEquals(case_val, 2);
- case_val = 22;
- });
- Rhum.testCase("hooks properly set case_val", () => {
- // Assert the value has changed from 22 to 2 (after setting it to 22 in the above case)
- Rhum.asserts.assertEquals(case_val, 22);
- Rhum.asserts.assertEquals(suite_val, "Ed");
- });
- });
-
- // Run the second test suite
- Rhum.testSuite("test suite 2", () => {
- Rhum.beforeAll(() => {
- case_val = 0;
- });
- Rhum.testCase("hooks properly set suite_val and case_val", async () => {
- // Asserting the hook should have replaced the name
- Rhum.asserts.assertEquals(suite_val, "Ed");
- Rhum.asserts.assertEquals(case_val, 0);
- });
- });
-
- Rhum.testSuite("test suite 3", () => {
- let async_case_val = 5;
- Rhum.beforeAll(async () => {
- await new Promise((resolve) => {
- setTimeout(() => resolve((async_case_val = 15)), 1000);
- });
- });
- Rhum.testCase("beforeAll hook can be async", () => {
- Rhum.asserts.assertEquals(suite_val, "Ed");
- Rhum.asserts.assertEquals(async_case_val, 15);
- });
- });
-});
-
-Rhum.run();
diff --git a/tests/integration/hooks/before_each_test.ts b/tests/integration/hooks/before_each_test.ts
deleted file mode 100644
index a6b5af48..00000000
--- a/tests/integration/hooks/before_each_test.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Purpose of this test is to ensure a test using before_each succeeds
- */
-
-import { Rhum } from "../../../mod.ts";
-
-let suite_val = "Ed";
-let case_val = 22;
-
-Rhum.testPlan("before_each_test.ts", () => {
- Rhum.beforeEach(() => {
- suite_val = "Eric";
- });
-
- // Run the first test suite
- Rhum.testSuite("test suite 1", () => {
- Rhum.beforeEach(() => {
- case_val = 2;
- });
-
- Rhum.testCase("suite_val is Eric", () => {
- Rhum.asserts.assertEquals(suite_val, "Eric");
- suite_val = "Ed";
- });
- Rhum.testCase("suite_val is Ed", () => {
- Rhum.asserts.assertEquals(suite_val, "Ed");
- });
- Rhum.testCase("case_val is 2", () => {
- Rhum.asserts.assertEquals(case_val, 2);
- case_val = 22;
- });
- Rhum.testCase("case_val is still 2", () => {
- Rhum.asserts.assertEquals(case_val, 2);
- });
- });
-
- // Run the second test suite
- Rhum.testSuite("test suite 2", () => {
- Rhum.beforeEach(() => {
- case_val = 0;
- });
-
- Rhum.testCase("suite_val is Eric", async () => {
- Rhum.asserts.assertEquals(suite_val, "Eric");
- suite_val = "Ed";
- });
- Rhum.testCase("suite_val is Ed", async () => {
- Rhum.asserts.assertEquals(suite_val, "Ed");
- case_val = 1;
- });
- Rhum.testCase("case_val is 0", () => {
- Rhum.asserts.assertEquals(case_val, 0);
- });
- });
-
- Rhum.testSuite("test suite 3", () => {
- let async_case_val = 5;
-
- Rhum.beforeEach(async () => {
- await new Promise((resolve) => {
- setTimeout(() => resolve((async_case_val = 15)), 1000);
- });
- });
-
- Rhum.testCase("beforeEach hook can be async", () => {
- Rhum.asserts.assertEquals(async_case_val, 15);
- });
- });
-});
-
-Rhum.run();
diff --git a/tests/integration/mock_test.ts b/tests/integration/mock_test.ts
index cda3cb92..a0675e82 100644
--- a/tests/integration/mock_test.ts
+++ b/tests/integration/mock_test.ts
@@ -23,6 +23,7 @@ class TestObject {
}
}
+De
Rhum.testPlan("mock_test.ts", () => {
Rhum.testSuite("mock()", () => {
Rhum.testCase("can mock an object", () => {
diff --git a/tests/unit/mod_test.ts b/tests/unit/mod_test.ts
deleted file mode 100644
index aa38d34f..00000000
--- a/tests/unit/mod_test.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import { StdAsserts as asserts } from "../../deps.ts";
-import { Rhum } from "../../mod.ts";
-
-// TODO(any) When this feature is properly implemented
-// Deno.test({
-// name: "Unit | Rhum | SetUp() | Assigns the correct callback",
-// async fn(): Promise {
-// function cb () {
-// return "Hello world!"
-// }
-// Rhum.SetUp(cb)
-// asserts.assertEquals(typeof Rhum.set_up_hook, "function")
-// if (Rhum.set_up_hook) {
-// asserts.assertEquals(Rhum.set_up_hook(), "Hello world!")
-// } else {
-// throw new Error("Rhum.set_up_hook should be defined")
-// }
-// }
-// })
-
-// TODO(any) When this feature is properly implemented
-// Deno.test({
-// name: "Unit | Rhum | Skip() | Correctly skips the test",
-// async fn(): Promise {
-//
-// }
-// })
-
-// TODO(any) When this feature is properly implemented
-// Deno.test({
-// name: "Unit | Rhum | TearDown() | Correctly tears down",
-// async fn(): Promise {
-//
-// }
-// })
-
-Deno.test({
- name: "Unit | Rhum | testCase() | Runs a test without failing",
- async fn(): Promise {
- Rhum.testPlan("test plan", () => {
- Rhum.testSuite("test suite", () => {
- Rhum.testCase("Testing testCase", () => {
- console.log("Running!");
- });
- });
- });
- },
-});
-
-Deno.test({
- name: "Unit | Rhum | testPlan() | Registers the test plan name",
- async fn(): Promise {
- let functionWasCalled = false;
- function testFn() {
- functionWasCalled = true;
- return "Hello world!";
- }
- const copyTestFn = testFn;
- Rhum.testPlan("Testing testCase", copyTestFn);
- Rhum.asserts.assert(functionWasCalled);
- },
-});
-
-Deno.test({
- name: "Unit | Rhum | testSuite() | Registers the test suite name",
- async fn(): Promise {
- let functionWasCalled = false;
- function testFn() {
- functionWasCalled = true;
- return "Hello world!";
- }
- const copyTestFn = testFn;
- Rhum.testSuite("Testing testCase", copyTestFn);
- asserts.assertEquals(functionWasCalled, true);
- },
-});
diff --git a/tests/unit/quick_start.ts b/tests/unit/quick_start.ts
deleted file mode 100644
index 561566a8..00000000
--- a/tests/unit/quick_start.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-// File: app_test.ts
-
-import { Rhum } from "../../mod.ts";
-
-let value = false;
-
-function run() {
- return true;
-}
-
-async function close() {
- value = true;
- return value;
-}
-
-Rhum.testPlan("app_test.ts", () => {
- // Run the first test suite
- Rhum.testSuite("run()", () => {
- Rhum.testCase("Returns true", () => {
- const result = run();
- Rhum.asserts.assertEquals(true, result);
- });
- });
- // Run the second test suite
- Rhum.testSuite("close()", () => {
- Rhum.testCase("Returns true", async () => {
- const result = await close();
- Rhum.asserts.assertEquals(true, result);
- });
- });
-});
-
-Rhum.run();
diff --git a/tests/unit/test_case_test.ts b/tests/unit/test_case_test.ts
deleted file mode 100644
index f8eaecde..00000000
--- a/tests/unit/test_case_test.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { StdAsserts as asserts } from "../../deps.ts";
-import { TestCase } from "../../src/test_case.ts";
-import { ITestPlan } from "../../src/interfaces.ts";
-
-Deno.test({
- name: "Unit | TestCase | run() | Runs the test without failing",
- async fn(): Promise {
- const plan: ITestPlan = {
- suites: {
- "suite_1": {
- cases: [{
- name: "case_1",
- new_name: "123 case_1",
- testFn: function () {
- asserts.assertEquals(true, true);
- },
- }],
- },
- },
- };
- const testCase = new TestCase(plan);
- await testCase.run();
- },
-});
From 38d2fd2dc7f5cf46d54254d1e3a757ef97bb6db6 Mon Sep 17 00:00:00 2001
From: Edward Bebbington
Date: Mon, 24 Jan 2022 23:24:43 +0000
Subject: [PATCH 02/62] add back mock builder and types
---
mod.ts | 1 -
src/mock_builder.ts | 164 +++++++++++++++++++++++++++++++++
src/types.ts | 18 ++++
tests/integration/mock_test.ts | 2 +-
4 files changed, 183 insertions(+), 2 deletions(-)
create mode 100644 src/mock_builder.ts
create mode 100644 src/types.ts
diff --git a/mod.ts b/mod.ts
index e0e7eb89..461bf765 100644
--- a/mod.ts
+++ b/mod.ts
@@ -5,7 +5,6 @@ export type { Constructor, Stubbed } from "./src/types.ts";
export { MockBuilder } from "./src/mock_builder.ts";
export class RhumRunner {
-
// FILE MARKER - METHODS - PUBLIC ////////////////////////////////////////////
/**
diff --git a/src/mock_builder.ts b/src/mock_builder.ts
new file mode 100644
index 00000000..3fa4a491
--- /dev/null
+++ b/src/mock_builder.ts
@@ -0,0 +1,164 @@
+import type { Constructor, Mocked } from "./types.ts";
+
+export class MockBuilder {
+ /**
+ * Properties of the class that is passed in
+ */
+ protected properties: string[] = [];
+
+ /**
+ * Functions of the class passed in
+ */
+ protected functions: string[] = [];
+
+ /**
+ * The class object passed into the constructor
+ */
+ protected constructor_fn: Constructor;
+
+ /**
+ * A list of arguments the class constructor takes
+ */
+ protected constructor_args: unknown[] = [];
+
+ //////////////////////////////////////////////////////////////////////////////
+ // FILE MARKER - CONSTRUCTOR /////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Construct an object of this class.
+ *
+ * @param constructorFn - The object's constructor function to instantiate.
+ */
+ constructor(constructorFn: Constructor) {
+ this.constructor_fn = constructorFn;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // FILE MARKER - METHODS - PUBLIC ////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Create the mock object.
+ *
+ * @returns A mocked object.
+ */
+ public create(): Mocked {
+ // deno-lint-ignore no-explicit-any
+ const mock: Mocked = {
+ calls: {},
+ is_mock: true,
+ };
+ const original = new this.constructor_fn(...this.constructor_args);
+
+ // Attach all of the original's properties to the mock
+ this.getAllProperties(original).forEach((property: string) => {
+ const desc = Object.getOwnPropertyDescriptor(original, property);
+ mock[property] = desc!.value;
+ });
+
+ // Attach all of the original's functions to the mock
+ this.getAllFunctions(original).forEach((method: string) => {
+ const nativeMethods = [
+ "__defineGetter__",
+ "__defineSetter__",
+ "__lookupGetter__",
+ "__lookupSetter__",
+ "constructor",
+ "hasOwnProperty",
+ "isPrototypeOf",
+ "propertyIsEnumerable",
+ "toLocaleString",
+ "toString",
+ "valueOf",
+ ];
+
+ if (nativeMethods.indexOf(method) == -1) {
+ if (!mock.calls[method]) {
+ mock.calls[method] = 0;
+ }
+ mock[method] = function () {
+ mock.calls[method]++;
+ return (original[method as keyof T] as unknown as (
+ ...params: unknown[]
+ ) => unknown)();
+ };
+ } else {
+ // copy nativeMethod directly without mocking
+ mock[method] = original[method as keyof T];
+ }
+ });
+
+ return mock;
+ }
+
+ /**
+ * Before constructing the mock object, track any constructur function args
+ * that need to be passed in when constructing the mock object.
+ *
+ * @param args - A rest parameter of arguments that will get passed in to
+ * the constructor function of the class being mocked.
+ *
+ * @returns `this` so that methods in this class can be chained.
+ */
+ public withConstructorArgs(...args: unknown[]): this {
+ this.constructor_args = args;
+ return this;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // FILE MARKER - METHODS - PROTECTED /////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get all properties--public, protected, private--from the object that will
+ * be mocked.
+ *
+ * @param obj - The object that will be mocked.
+ *
+ * @returns An array of the object's properties.
+ */
+ protected getAllProperties(obj: T): string[] {
+ let functions: string[] = [];
+ let clone = obj;
+ do {
+ functions = functions.concat(Object.getOwnPropertyNames(clone));
+ } while ((clone = Object.getPrototypeOf(clone)));
+
+ return functions.sort().filter(
+ function (e: string, i: number, arr: unknown[]) {
+ if (
+ e != arr[i + 1] && typeof obj[e as keyof T] != "function"
+ ) {
+ return true;
+ }
+ },
+ );
+ }
+
+ /**
+ * Get all functions--public, protected, private--from the object that will be
+ * mocked.
+ *
+ * @param obj - The object that will be mocked.
+ *
+ * @returns An array of the object's functions.
+ */
+ protected getAllFunctions(obj: T): string[] {
+ let functions: string[] = [];
+ let clone = obj;
+ do {
+ functions = functions.concat(Object.getOwnPropertyNames(clone));
+ } while ((clone = Object.getPrototypeOf(clone)));
+
+ return functions.sort().filter(
+ function (e: string, i: number, arr: unknown[]) {
+ if (
+ e != arr[i + 1] && typeof obj[e as keyof T] == "function"
+ ) {
+ return true;
+ }
+ },
+ );
+ }
+}
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 00000000..4c6125a6
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,18 @@
+export type TConstructorFunction = {
+ new (...args: unknown[]): T;
+ [key: string]: unknown;
+};
+
+// deno-lint-ignore no-explicit-any
+export type Constructor = new (...args: any[]) => T;
+
+export type Mocked = T & {
+ calls: { [k in keyof T]: T[k] extends () => void ? number : never };
+ is_mock: true;
+};
+
+export type Stubbed = T & {
+ calls: { [k in keyof T]?: T[k] extends () => void ? number : never };
+ stub: (p: string, v: unknown) => void;
+ is_stubbed: true;
+};
diff --git a/tests/integration/mock_test.ts b/tests/integration/mock_test.ts
index a0675e82..46ff2d97 100644
--- a/tests/integration/mock_test.ts
+++ b/tests/integration/mock_test.ts
@@ -23,7 +23,7 @@ class TestObject {
}
}
-De
+De;
Rhum.testPlan("mock_test.ts", () => {
Rhum.testSuite("mock()", () => {
Rhum.testCase("can mock an object", () => {
From cc0cc1d63a8851e623e080d0ba914047a7c18ab3 Mon Sep 17 00:00:00 2001
From: Edward Bebbington
Date: Mon, 24 Jan 2022 23:34:56 +0000
Subject: [PATCH 03/62] fmt
---
tests/deps.ts | 2 +-
tests/integration/mock_test.ts | 137 ++++++++++++++++-----------------
tests/integration/stub_test.ts | 16 ++--
3 files changed, 77 insertions(+), 78 deletions(-)
diff --git a/tests/deps.ts b/tests/deps.ts
index cf3c3844..906a5b56 100644
--- a/tests/deps.ts
+++ b/tests/deps.ts
@@ -1 +1 @@
-export { assertEquals } from "https://deno.land/std@0.116.0/testing/asserts.ts";
+export { assertEquals } from "https://deno.land/std@0.116.0/testing/asserts.ts";
diff --git a/tests/integration/mock_test.ts b/tests/integration/mock_test.ts
index efcd3530..84d8d723 100644
--- a/tests/integration/mock_test.ts
+++ b/tests/integration/mock_test.ts
@@ -1,5 +1,5 @@
import { Rhum } from "../../mod.ts";
-import { assertEquals } from "../deps.ts"
+import { assertEquals } from "../deps.ts";
class MathService {
public add(num1: number, num2: number): number {
@@ -24,7 +24,6 @@ class TestObject {
}
}
-
class TestRequestHandler {
// deno-lint-ignore require-await
async handle(request: Request): Promise {
@@ -43,7 +42,7 @@ class TestRequestHandler {
}
}
-Deno.test("mock()", t => {
+Deno.test("mock()", (t) => {
t.step({
name: "can mock an object",
fn() {
@@ -52,83 +51,83 @@ Deno.test("mock()", t => {
.create();
assertEquals(mock.constructor.name, "TestObject");
assertEquals(mock.is_mock, true);
- }
- })
-
+ },
+ });
+
t.step("Can mock an object with constructor args", () => {
const mock = Rhum
- .mock(TestObject)
- .withConstructorArgs("my server", new MathService())
- .create();
- assertEquals(mock.constructor.name, "TestObject");
- assertEquals(mock.is_mock, true);
- assertEquals(mock.name, "my server");
- })
+ .mock(TestObject)
+ .withConstructorArgs("my server", new MathService())
+ .create();
+ assertEquals(mock.constructor.name, "TestObject");
+ assertEquals(mock.is_mock, true);
+ assertEquals(mock.name, "my server");
+ });
t.step("can access protected property", () => {
const mock = Rhum
- .mock(TestObject)
- .create();
- assertEquals(
- (mock as unknown as { [key: string]: string }).protected_property,
- "I AM PROTECTED PROPERTY.",
- );
- })
+ .mock(TestObject)
+ .create();
+ assertEquals(
+ (mock as unknown as { [key: string]: string }).protected_property,
+ "I AM PROTECTED PROPERTY.",
+ );
+ });
- t.step('Can access protected method', () => {
+ t.step("Can access protected method", () => {
const mock = Rhum
- .mock(TestObject)
- .create();
- assertEquals(
- (mock as unknown as { [key: string]: () => string }).protectedMethod(),
- "I AM A PROTECTED METHOD.",
- );
- })
+ .mock(TestObject)
+ .create();
+ assertEquals(
+ (mock as unknown as { [key: string]: () => string }).protectedMethod(),
+ "I AM A PROTECTED METHOD.",
+ );
+ });
- t.step('has mocked math service', () => {
+ t.step("has mocked math service", () => {
const mockMathService = Rhum
- .mock(MathService)
- .create();
- const mockTestObject = Rhum
- .mock(TestObject)
- .withConstructorArgs("has mocked math service", mockMathService)
- .create();
- assertEquals(mockMathService.calls.add, 0);
- mockTestObject.sum(1, 1);
- assertEquals(mockMathService.calls.add, 1);
- })
+ .mock(MathService)
+ .create();
+ const mockTestObject = Rhum
+ .mock(TestObject)
+ .withConstructorArgs("has mocked math service", mockMathService)
+ .create();
+ assertEquals(mockMathService.calls.add, 0);
+ mockTestObject.sum(1, 1);
+ assertEquals(mockMathService.calls.add, 1);
+ });
- t.step('Native request mock', async () => {
+ t.step("Native request mock", async () => {
const router = Rhum.mock(TestRequestHandler).create();
- const reqPost = new Request("https://google.com", {
- method: "post",
- headers: {
- "content-type": "application/json",
- },
- });
- assertEquals(router.calls.handle, 0);
- assertEquals(await router.handle(reqPost), "posted");
- assertEquals(router.calls.handle, 1);
+ const reqPost = new Request("https://google.com", {
+ method: "post",
+ headers: {
+ "content-type": "application/json",
+ },
+ });
+ assertEquals(router.calls.handle, 0);
+ assertEquals(await router.handle(reqPost), "posted");
+ assertEquals(router.calls.handle, 1);
- const reqPostNotJson = new Request("https://google.com", {
- method: "post",
- });
- assertEquals(router.calls.handle, 1);
- assertEquals(
- await router.handle(reqPostNotJson),
- "Content-Type is incorrect",
- );
- assertEquals(router.calls.handle, 2);
+ const reqPostNotJson = new Request("https://google.com", {
+ method: "post",
+ });
+ assertEquals(router.calls.handle, 1);
+ assertEquals(
+ await router.handle(reqPostNotJson),
+ "Content-Type is incorrect",
+ );
+ assertEquals(router.calls.handle, 2);
- const reqGet = new Request("https://google.com", {
- method: "get",
- });
- assertEquals(router.calls.handle, 2);
- assertEquals(
- await router.handle(reqGet),
- "Method is not post",
- );
- assertEquals(router.calls.handle, 3);
- })
-})
+ const reqGet = new Request("https://google.com", {
+ method: "get",
+ });
+ assertEquals(router.calls.handle, 2);
+ assertEquals(
+ await router.handle(reqGet),
+ "Method is not post",
+ );
+ assertEquals(router.calls.handle, 3);
+ });
+});
diff --git a/tests/integration/stub_test.ts b/tests/integration/stub_test.ts
index ec50154e..90ac247c 100644
--- a/tests/integration/stub_test.ts
+++ b/tests/integration/stub_test.ts
@@ -1,5 +1,5 @@
import { Rhum } from "../../mod.ts";
-import { assertEquals } from "../deps.ts"
+import { assertEquals } from "../deps.ts";
class Server {
public greeting = "hello";
@@ -12,12 +12,12 @@ class Server {
}
}
-Deno.test("stub()", t => {
- t.step('Can stub a propert', () => {
+Deno.test("stub()", (t) => {
+ t.step("Can stub a propert", () => {
const server = Rhum.stubbed(new Server());
- server.stub("greeting", "you got changed");
- assertEquals(server.greeting, "you got changed");
- })
+ server.stub("greeting", "you got changed");
+ assertEquals(server.greeting, "you got changed");
+ });
t.step("Can stub a method", () => {
const server = Rhum.stubbed(new Server());
@@ -32,5 +32,5 @@ Deno.test("stub()", t => {
server.is_stubbed,
true,
);
- })
-})
\ No newline at end of file
+ });
+});
From 83e749382e03c86ddc8b90cb6681926716af8de8 Mon Sep 17 00:00:00 2001
From: Edward Bebbington
Date: Mon, 24 Jan 2022 23:38:56 +0000
Subject: [PATCH 04/62] fix tests
---
tests/integration/mock_test.ts | 14 ++++++------
tests/integration/stub_test.ts | 6 ++---
tests/unit/mock_builder_test.ts | 40 +++++++++++++++++----------------
3 files changed, 31 insertions(+), 29 deletions(-)
diff --git a/tests/integration/mock_test.ts b/tests/integration/mock_test.ts
index 84d8d723..492058a7 100644
--- a/tests/integration/mock_test.ts
+++ b/tests/integration/mock_test.ts
@@ -42,8 +42,8 @@ class TestRequestHandler {
}
}
-Deno.test("mock()", (t) => {
- t.step({
+Deno.test("mock()", async (t) => {
+ await t.step({
name: "can mock an object",
fn() {
const mock = Rhum
@@ -54,7 +54,7 @@ Deno.test("mock()", (t) => {
},
});
- t.step("Can mock an object with constructor args", () => {
+ await t.step("Can mock an object with constructor args", () => {
const mock = Rhum
.mock(TestObject)
.withConstructorArgs("my server", new MathService())
@@ -64,7 +64,7 @@ Deno.test("mock()", (t) => {
assertEquals(mock.name, "my server");
});
- t.step("can access protected property", () => {
+ await t.step("can access protected property", () => {
const mock = Rhum
.mock(TestObject)
.create();
@@ -74,7 +74,7 @@ Deno.test("mock()", (t) => {
);
});
- t.step("Can access protected method", () => {
+ await t.step("Can access protected method", () => {
const mock = Rhum
.mock(TestObject)
.create();
@@ -84,7 +84,7 @@ Deno.test("mock()", (t) => {
);
});
- t.step("has mocked math service", () => {
+ await t.step("has mocked math service", () => {
const mockMathService = Rhum
.mock(MathService)
.create();
@@ -97,7 +97,7 @@ Deno.test("mock()", (t) => {
assertEquals(mockMathService.calls.add, 1);
});
- t.step("Native request mock", async () => {
+ await t.step("Native request mock", async () => {
const router = Rhum.mock(TestRequestHandler).create();
const reqPost = new Request("https://google.com", {
diff --git a/tests/integration/stub_test.ts b/tests/integration/stub_test.ts
index 90ac247c..9aeaf5aa 100644
--- a/tests/integration/stub_test.ts
+++ b/tests/integration/stub_test.ts
@@ -12,14 +12,14 @@ class Server {
}
}
-Deno.test("stub()", (t) => {
- t.step("Can stub a propert", () => {
+Deno.test("stub()", async (t) => {
+ await t.step("Can stub a propert", () => {
const server = Rhum.stubbed(new Server());
server.stub("greeting", "you got changed");
assertEquals(server.greeting, "you got changed");
});
- t.step("Can stub a method", () => {
+ await t.step("Can stub a method", () => {
const server = Rhum.stubbed(new Server());
server.stub("methodThatLogs", () => {
return "don't run the console.log()";
diff --git a/tests/unit/mock_builder_test.ts b/tests/unit/mock_builder_test.ts
index cf1d81d8..68fce244 100644
--- a/tests/unit/mock_builder_test.ts
+++ b/tests/unit/mock_builder_test.ts
@@ -1,31 +1,33 @@
-import { StdAsserts as asserts } from "../../deps.ts";
+import { assertEquals } from "../deps.ts";
import { MockBuilder } from "../../src/mock_builder.ts";
Deno.test({
name: "Unit | MockBuilder | constructor() | returns a MockBuilder object",
fn(): void {
const mock = new MockBuilder(TestObjectOne);
- asserts.assertEquals(mock.constructor.name, "MockBuilder");
+ assertEquals(mock.constructor.name, "MockBuilder");
},
});
-Deno.test({
- name: "Unit | MockBuilder | create() | creates mock without constructor args",
- fn(): void {
- const mock = new MockBuilder(TestObjectTwo)
- .create();
- asserts.assertEquals(mock.name, undefined);
- },
-});
-
-Deno.test({
- name: "Unit | MockBuilder | create() | creates mock with constructor args",
- fn(): void {
- const mock = new MockBuilder(TestObjectTwo)
- .withConstructorArgs("some name")
- .create();
- asserts.assertEquals(mock.name, "some name");
- },
+Deno.test("create()", async (t) => {
+ await t.step({
+ name: "create() | creates mock without constructor args",
+ fn(): void {
+ const mock = new MockBuilder(TestObjectTwo)
+ .create();
+ assertEquals(mock.name, undefined);
+ },
+ });
+
+ await t.step({
+ name: "Unit | MockBuilder | create() | creates mock with constructor args",
+ fn(): void {
+ const mock = new MockBuilder(TestObjectTwo)
+ .withConstructorArgs("some name")
+ .create();
+ assertEquals(mock.name, "some name");
+ },
+ });
});
// TODO(crookse) Write this test when we can invoke non-public members.
From 809b168aa9f84b1aa222e636b723b8082483e438 Mon Sep 17 00:00:00 2001
From: Edward Bebbington
Date: Mon, 24 Jan 2022 23:39:53 +0000
Subject: [PATCH 05/62] tpo
---
egg.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/egg.json b/egg.json
index b3578378..08dce5ea 100644
--- a/egg.json
+++ b/egg.json
@@ -1,6 +1,6 @@
{
"name": "rhum",
- "description": "A test double mdoule to stub and mock your code.",
+ "description": "A test double module to stub and mock your code.",
"version": "1.1.11",
"stable": true,
"repository": "https://github.com/drashland/rhum",
From ee9f820643d02e76a416961a815b5a5509db20fd Mon Sep 17 00:00:00 2001
From: Edward Bebbington
Date: Sat, 9 Apr 2022 14:46:52 +0100
Subject: [PATCH 06/62] use mock and stub
---
mod.ts | 117 +++++++++++++++------------------
src/mock_builder.ts | 2 +-
tests/integration/mock_test.ts | 35 ++++------
tests/integration/stub_test.ts | 6 +-
4 files changed, 70 insertions(+), 90 deletions(-)
diff --git a/mod.ts b/mod.ts
index 461bf765..3ee3ac39 100644
--- a/mod.ts
+++ b/mod.ts
@@ -4,71 +4,60 @@ import { MockBuilder } from "./src/mock_builder.ts";
export type { Constructor, Stubbed } from "./src/types.ts";
export { MockBuilder } from "./src/mock_builder.ts";
-export class RhumRunner {
- // FILE MARKER - METHODS - PUBLIC ////////////////////////////////////////////
-
- /**
- * Stub a member of an object.
- *
- * @param obj -The object containing the member to stub.
- * @param member -The member to stub.
- * @param value - The return value of the stubbed member.
- *
- * Returns the object in question as a Stubbed type. Being a Stubbed type
- * means it has access to a `.stub()` method for stubbing properties and
- * methods.
- *
- * class MyObject {
- * public some_property = "someValue";
- * }
- *
- * // Define the object that will have stubbed members as a stubbed object
- * const myStubbedObject = Rhum.stubbed(new MyObject());
- *
- * // Stub the object's some_property property to a certain value
- * myStubbedObject.stub("some_property", "this property is now stubbed");
- *
- * // Assert that the property was stubbed
- * Rhum.asserts.assertEquals(myStubbedObject.some_property, "this property is now stubbed");
- */
- public stubbed(obj: T): Stubbed {
- (obj as unknown as { [key: string]: boolean }).is_stubbed = true;
- (obj as unknown as {
- [key: string]: (property: string, value: unknown) => void;
- }).stub = function (
- property: string,
- value: unknown,
- ): void {
- Object.defineProperty(obj, property, {
- value: value,
- });
- };
-
- return obj as Stubbed;
- }
-
- /**
- * Get the mock builder to mock classes.
- *
- * @param constructorFn - The constructor function of the object to mock.
- *
- * Returns an instance of the MockBuilder class.
- *
- * class ToBeMocked { ... }
- *
- * const mock = Rhum
- * .mock(ToBeMocked)
- * .withConstructorArgs("someArg") // if the class to be mocked has a constructor and it requires args
- * .create();
- */
- public mock(constructorFn: Constructor): MockBuilder {
- return new MockBuilder(constructorFn);
- }
-}
+/**
+ * Get the mock builder to mock classes.
+ *
+ * @param constructorFn - The constructor function of the object to mock.
+ *
+ * Returns an instance of the MockBuilder class.
+ *
+ * class ToBeMocked { ... }
+ *
+ * const mock = Rhum
+ * .mock(ToBeMocked)
+ * .withConstructorArgs("someArg") // if the class to be mocked has a constructor and it requires args
+ * .create();
+ */
+export const Mock = (constructorFn: Constructor): MockBuilder => {
+ return new MockBuilder(constructorFn);
+};
/**
- * An instance of the RhumRunner.
+ * Stub a member of an object.
+ *
+ * @param obj -The object containing the member to stub.
+ * @param member -The member to stub.
+ * @param value - The return value of the stubbed member.
+ *
+ * Returns the object in question as a Stubbed type. Being a Stubbed type
+ * means it has access to a `.stub()` method for stubbing properties and
+ * methods.
+ *
+ * class MyObject {
+ * public some_property = "someValue";
+ * }
*
- * const Rhum = new RhumRunner();
+ * // Define the object that will have stubbed members as a stubbed object
+ * const myStubbedObject = Rhum.stubbed(new MyObject());
+ *
+ * // Stub the object's some_property property to a certain value
+ * myStubbedObject.stub("some_property", "this property is now stubbed");
+ *
+ * // Assert that the property was stubbed
+ * Rhum.asserts.assertEquals(myStubbedObject.some_property, "this property is now stubbed");
*/
-export const Rhum = new RhumRunner();
+export const Stub = (obj: T): Stubbed => {
+ (obj as unknown as { [key: string]: boolean }).is_stubbed = true;
+ (obj as unknown as {
+ [key: string]: (property: string, value: unknown) => void;
+ }).stub = function (
+ property: string,
+ value: unknown,
+ ): void {
+ Object.defineProperty(obj, property, {
+ value: value,
+ });
+ };
+
+ return obj as Stubbed;
+};
diff --git a/src/mock_builder.ts b/src/mock_builder.ts
index bcc8db8f..174d8ac8 100644
--- a/src/mock_builder.ts
+++ b/src/mock_builder.ts
@@ -86,7 +86,7 @@ export class MockBuilder {
mock.calls[method]++;
// Make sure the method calls its original self
- let unbound = (original[method as keyof T] as unknown as (
+ const unbound = (original[method as keyof T] as unknown as (
...params: unknown[]
) => unknown);
diff --git a/tests/integration/mock_test.ts b/tests/integration/mock_test.ts
index ff327831..3ca57de9 100644
--- a/tests/integration/mock_test.ts
+++ b/tests/integration/mock_test.ts
@@ -1,11 +1,11 @@
-import { Rhum } from "../../mod.ts";
+import { Mock } from "../../mod.ts";
import { assertEquals } from "../deps.ts";
class MathService {
public add(
num1: number,
num2: number,
- useNestedAdd: boolean = false,
+ useNestedAdd = false,
): number {
if (useNestedAdd) {
return this.nestedAdd(num1, num2);
@@ -30,7 +30,7 @@ class TestObject {
public sum(
num1: number,
num2: number,
- useNestedAdd: boolean = false,
+ useNestedAdd = false,
): number {
const sum = this.math_service.add(num1, num2, useNestedAdd);
return sum;
@@ -70,8 +70,7 @@ Deno.test("mock()", async (t) => {
await t.step({
name: "can mock an object",
fn() {
- const mock = Rhum
- .mock(TestObject)
+ const mock = Mock(TestObject)
.create();
assertEquals(mock.constructor.name, "TestObject");
assertEquals(mock.is_mock, true);
@@ -79,8 +78,7 @@ Deno.test("mock()", async (t) => {
});
await t.step("Can mock an object with constructor args", () => {
- const mock = Rhum
- .mock(TestObject)
+ const mock = Mock(TestObject)
.withConstructorArgs("my server", new MathService())
.create();
assertEquals(mock.constructor.name, "TestObject");
@@ -89,8 +87,7 @@ Deno.test("mock()", async (t) => {
});
await t.step("can access protected property", () => {
- const mock = Rhum
- .mock(TestObject)
+ const mock = Mock(TestObject)
.create();
assertEquals(
(mock as unknown as { [key: string]: string }).protected_property,
@@ -99,8 +96,7 @@ Deno.test("mock()", async (t) => {
});
await t.step("Can access protected method", () => {
- const mock = Rhum
- .mock(TestObject)
+ const mock = Mock(TestObject)
.create();
assertEquals(
(mock as unknown as { [key: string]: () => string }).protectedMethod(),
@@ -109,11 +105,9 @@ Deno.test("mock()", async (t) => {
});
await t.step("has mocked math service", () => {
- const mockMathService = Rhum
- .mock(MathService)
+ const mockMathService = Mock(MathService)
.create();
- const mockTestObject = Rhum
- .mock(TestObject)
+ const mockTestObject = Mock(TestObject)
.withConstructorArgs("has mocked math service", mockMathService)
.create();
assertEquals(mockMathService.calls.add, 0);
@@ -122,11 +116,9 @@ Deno.test("mock()", async (t) => {
});
await t.step("call count for outside nested function is increased", () => {
- const mockMathService = Rhum
- .mock(MathService)
+ const mockMathService = Mock(MathService)
.create();
- const mockTestObject = Rhum
- .mock(TestObject)
+ const mockTestObject = Mock(TestObject)
.withConstructorArgs("has mocked math service", mockMathService)
.create();
assertEquals(mockMathService.calls.add, 0);
@@ -137,15 +129,14 @@ Deno.test("mock()", async (t) => {
});
await t.step("can mock getters and setters", () => {
- const mock = Rhum
- .mock(TestObject)
+ const mock = Mock(TestObject)
.create();
mock.age = 999;
assertEquals(mock.age, 999);
});
await t.step("Native request mock", async () => {
- const router = Rhum.mock(TestRequestHandler).create();
+ const router = Mock(TestRequestHandler).create();
const reqPost = new Request("https://google.com", {
method: "post",
diff --git a/tests/integration/stub_test.ts b/tests/integration/stub_test.ts
index 9aeaf5aa..e19dff0b 100644
--- a/tests/integration/stub_test.ts
+++ b/tests/integration/stub_test.ts
@@ -1,4 +1,4 @@
-import { Rhum } from "../../mod.ts";
+import { Stub } from "../../mod.ts";
import { assertEquals } from "../deps.ts";
class Server {
@@ -14,13 +14,13 @@ class Server {
Deno.test("stub()", async (t) => {
await t.step("Can stub a propert", () => {
- const server = Rhum.stubbed(new Server());
+ const server = Stub(new Server());
server.stub("greeting", "you got changed");
assertEquals(server.greeting, "you got changed");
});
await t.step("Can stub a method", () => {
- const server = Rhum.stubbed(new Server());
+ const server = Stub(new Server());
server.stub("methodThatLogs", () => {
return "don't run the console.log()";
});
From 289c455c79b2768e0fa8adb599894c6a5ab32673 Mon Sep 17 00:00:00 2001
From: Eric Crooks
Date: Sun, 10 Apr 2022 12:08:05 -0400
Subject: [PATCH 07/62] feat(mock): add mock.method(...).willReturn(...);
---
mod.ts | 28 +---
src/mock.ts | 89 +++++++++++
src/mock_builder.ts | 252 ++++++++++++++++++++++----------
src/pre_programmed_method.ts | 51 +++++++
src/types.ts | 17 +--
tests/deps.ts | 5 +-
tests/unit/mock_builder_test.ts | 145 +++++++++++++-----
7 files changed, 433 insertions(+), 154 deletions(-)
create mode 100644 src/mock.ts
create mode 100644 src/pre_programmed_method.ts
diff --git a/mod.ts b/mod.ts
index 3ee3ac39..b04125a1 100644
--- a/mod.ts
+++ b/mod.ts
@@ -5,18 +5,11 @@ export type { Constructor, Stubbed } from "./src/types.ts";
export { MockBuilder } from "./src/mock_builder.ts";
/**
- * Get the mock builder to mock classes.
+ * Get the builder to help you create mocked objects.
*
* @param constructorFn - The constructor function of the object to mock.
*
- * Returns an instance of the MockBuilder class.
- *
- * class ToBeMocked { ... }
- *
- * const mock = Rhum
- * .mock(ToBeMocked)
- * .withConstructorArgs("someArg") // if the class to be mocked has a constructor and it requires args
- * .create();
+ * @returns Instance of `MockBuilder`.
*/
export const Mock = (constructorFn: Constructor): MockBuilder => {
return new MockBuilder(constructorFn);
@@ -28,23 +21,6 @@ export const Mock = (constructorFn: Constructor): MockBuilder => {
* @param obj -The object containing the member to stub.
* @param member -The member to stub.
* @param value - The return value of the stubbed member.
- *
- * Returns the object in question as a Stubbed type. Being a Stubbed type
- * means it has access to a `.stub()` method for stubbing properties and
- * methods.
- *
- * class MyObject {
- * public some_property = "someValue";
- * }
- *
- * // Define the object that will have stubbed members as a stubbed object
- * const myStubbedObject = Rhum.stubbed(new MyObject());
- *
- * // Stub the object's some_property property to a certain value
- * myStubbedObject.stub("some_property", "this property is now stubbed");
- *
- * // Assert that the property was stubbed
- * Rhum.asserts.assertEquals(myStubbedObject.some_property, "this property is now stubbed");
*/
export const Stub = (obj: T): Stubbed => {
(obj as unknown as { [key: string]: boolean }).is_stubbed = true;
diff --git a/src/mock.ts b/src/mock.ts
new file mode 100644
index 00000000..362fbe08
--- /dev/null
+++ b/src/mock.ts
@@ -0,0 +1,89 @@
+import type { MethodCalls, MethodOf } from "./types.ts";
+import { PreProgrammedMethod } from "./pre_programmed_method.ts";
+
+class MockError extends Error {}
+
+/**
+ * Class to mock an object. This class is the original object with data members
+ * that can be used for verifying behavior (e.g., using `mock.calls.someMethod`
+ * to see how many `someMethod` was called).
+ */
+export class Mock {
+ readonly #calls: MethodCalls;
+ is_mock = true;
+ #original: OriginalObject;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // FILE MARKER - CONSTRUCTOR /////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @param original - The original object to mock.
+ * @param methodsToTrack - The original object's method to make trackable.
+ */
+ constructor(original: OriginalObject, methodsToTrack: string[]) {
+ this.#original = original;
+ this.#calls = this.#constructCallsProperty(methodsToTrack);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // FILE MARKER - GETTERS / SETTERS ///////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+
+ get calls(): MethodCalls {
+ return this.#calls;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // FILE MARKER - METHODS - PUBLIC ////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Pre-program a method on the original to return a specific value.
+ * @param methodName The method name on the original.
+ * @returns A pre-programmed method that will be called instead of original.
+ */
+ public method(
+ methodName: MethodOf,
+ ): PreProgrammedMethod {
+ const methodConfiguration = new PreProgrammedMethod<
+ OriginalObject,
+ MethodReturnValue
+ >(
+ methodName,
+ );
+
+ if (!((methodName as string) in this.#original)) {
+ throw new MockError(
+ `Method "${methodName}" does not exist.`,
+ );
+ }
+
+ Object.defineProperty(this.#original, methodName, {
+ value: methodConfiguration,
+ writable: true,
+ });
+
+ return methodConfiguration;
+ }
+
+ /**
+ * Construct the calls property. Only construct it, do not set it. The
+ * constructor will set it.
+ * @param methodsToTrack - All of the methods on the original object to make
+ * trackable.
+ * @returns - Key-value object where the key is the method name and the value
+ * is the number of calls. All calls start at 0.
+ */
+ #constructCallsProperty(
+ methodsToTrack: string[],
+ ): Record {
+ const calls: Partial> = {};
+
+ methodsToTrack.map((key: string) => {
+ calls[key as keyof OriginalObject] = 0;
+ });
+
+ return calls as Record;
+ }
+}
diff --git a/src/mock_builder.ts b/src/mock_builder.ts
index 174d8ac8..2b69de0b 100644
--- a/src/mock_builder.ts
+++ b/src/mock_builder.ts
@@ -1,25 +1,23 @@
-import type { Constructor, Mocked } from "./types.ts";
-
-export class MockBuilder {
- /**
- * Properties of the class that is passed in
- */
- protected properties: string[] = [];
-
- /**
- * Functions of the class passed in
- */
- protected functions: string[] = [];
+import type { Constructor } from "./types.ts";
+import { Mock } from "./mock.ts";
+import { PreProgrammedMethod } from "./pre_programmed_method.ts";
+/**
+ * Builder to help build a mock object. This does all of the heavy-lifting to
+ * create a mock object. Its `create()` method returns an instance of `Mock`,
+ * which is basically an original object with added data members for verifying
+ * behavior.
+ */
+export class MockBuilder {
/**
* The class object passed into the constructor
*/
- protected constructor_fn: Constructor;
+ #constructor_fn: Constructor;
/**
* A list of arguments the class constructor takes
*/
- protected constructor_args: unknown[] = [];
+ #constructor_args: unknown[] = [];
//////////////////////////////////////////////////////////////////////////////
// FILE MARKER - CONSTRUCTOR /////////////////////////////////////////////////
@@ -28,10 +26,10 @@ export class MockBuilder {
/**
* Construct an object of this class.
*
- * @param constructorFn - The object's constructor function to instantiate.
+ * @param constructorFn - The constructor function of the object to mock.
*/
- constructor(constructorFn: Constructor) {
- this.constructor_fn = constructorFn;
+ constructor(constructorFn: Constructor) {
+ this.#constructor_fn = constructorFn;
}
//////////////////////////////////////////////////////////////////////////////
@@ -41,88 +39,182 @@ export class MockBuilder {
/**
* Create the mock object.
*
- * @returns A mocked object.
+ * @returns The original object with capabilities from the Mock class.
*/
- public create(): Mocked {
- // deno-lint-ignore no-explicit-any
- const mock: Mocked = {
- calls: {},
- is_mock: true,
- };
- const original = new this.constructor_fn(...this.constructor_args);
+ public create(): ClassToMock & Mock {
+ const original = new this.#constructor_fn(...this.#constructor_args);
+
+ const mock = new Mock(
+ original,
+ this.#getAllFunctionNames(original),
+ );
// Attach all of the original's properties to the mock
- this.getAllProperties(original).forEach((property: string) => {
- const desc = Object.getOwnPropertyDescriptor(original, property) ??
- Object.getOwnPropertyDescriptor(
- this.constructor_fn.prototype,
- property,
- );
- mock[property] = desc!.value;
+ this.#getAllPropertyNames(original).forEach((property: string) => {
+ this.#addOriginalObjectPropertyToMockObject(original, mock, property);
});
// Attach all of the original's functions to the mock
- this.getAllFunctions(original).forEach((method: string) => {
- const nativeMethods = [
- "__defineGetter__",
- "__defineSetter__",
- "__lookupGetter__",
- "__lookupSetter__",
- "constructor",
- "hasOwnProperty",
- "isPrototypeOf",
- "propertyIsEnumerable",
- "toLocaleString",
- "toString",
- "valueOf",
- ];
-
- if (nativeMethods.indexOf(method) == -1) {
- if (!mock.calls[method]) {
- mock.calls[method] = 0;
- }
- mock[method] = function (...args: unknown[]) {
- // Add tracking
- mock.calls[method]++;
-
- // Make sure the method calls its original self
- const unbound = (original[method as keyof T] as unknown as (
- ...params: unknown[]
- ) => unknown);
-
- // When method calls its original self, let `this` be the mock. Reason
- // being the mock has tracking and the original does not.
- const bound = unbound.bind(mock);
-
- return bound(...args);
- };
- } else {
- // copy nativeMethod directly without mocking
- mock[method] = original[method as keyof T];
- }
+ this.#getAllFunctionNames(original).forEach((method: string) => {
+ this.#addOriginalObjectMethodToMockObject(
+ original,
+ mock,
+ method,
+ );
});
- return mock;
+ return mock as ClassToMock & Mock;
}
/**
- * Before constructing the mock object, track any constructur function args
+ * Before constructing the mock object, track any constructor function args
* that need to be passed in when constructing the mock object.
*
- * @param args - A rest parameter of arguments that will get passed in to
- * the constructor function of the class being mocked.
+ * @param args - A rest parameter of arguments that will get passed in to the
+ * constructor function of the object being mocked.
*
* @returns `this` so that methods in this class can be chained.
*/
public withConstructorArgs(...args: unknown[]): this {
- this.constructor_args = args;
+ this.#constructor_args = args;
return this;
}
//////////////////////////////////////////////////////////////////////////////
- // FILE MARKER - METHODS - PROTECTED /////////////////////////////////////////
+ // FILE MARKER - METHODS - PRIVATE ///////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
+ /**
+ * Add an original object's method to a mock object without doing anything
+ * else.
+ *
+ * @param original - The original object containing the method to mock.
+ * @param mock - The mock object receiving the method to mock.
+ * @param method - The name of the method to mock -- callable via
+ * `mock[method](...)`.
+ */
+ #addMethodToMockObject(
+ original: ClassToMock,
+ mock: Mock,
+ method: string,
+ ): void {
+ Object.defineProperty(mock, method, {
+ value: original[method as keyof ClassToMock],
+ });
+ }
+
+ /**
+ * Add an original object's method to a mock object -- determining whether the
+ * method should or should not be trackable.
+ *
+ * @param original - The original object containing the method to add.
+ * @param mock - The mock object receiving the method.
+ * @param method - The name of the method to mock -- callable via
+ * `mock[method](...)`.
+ */
+ #addOriginalObjectMethodToMockObject(
+ original: ClassToMock,
+ mock: Mock,
+ method: string,
+ ): void {
+ const nativeMethods = [
+ "__defineGetter__",
+ "__defineSetter__",
+ "__lookupGetter__",
+ "__lookupSetter__",
+ "constructor",
+ "hasOwnProperty",
+ "isPrototypeOf",
+ "propertyIsEnumerable",
+ "toLocaleString",
+ "toString",
+ "valueOf",
+ ];
+
+ // If this is a native method, then do not do anything fancy. Just add it to
+ // the mock.
+ if (nativeMethods.includes(method as string)) {
+ return this.#addMethodToMockObject(
+ original,
+ mock,
+ method,
+ );
+ }
+
+ // Otherwise, make the method trackable via `.calls` usage.
+ this.#addTrackableMethodToMockObject(
+ original,
+ mock,
+ method as keyof ClassToMock,
+ );
+ }
+
+ /**
+ * Add an original object's property to a mock object.
+ *
+ * @param original The original object containing the property.
+ * @param mock The mock object receiving the property.
+ * @param property The name of the property -- retrievable via
+ * `mock[property]`.
+ */
+ #addOriginalObjectPropertyToMockObject(
+ original: ClassToMock,
+ mock: Mock,
+ property: string,
+ ): void {
+ const desc = Object.getOwnPropertyDescriptor(original, property) ??
+ Object.getOwnPropertyDescriptor(
+ this.#constructor_fn.prototype,
+ property,
+ );
+
+ Object.defineProperty(mock, property, {
+ value: desc!.value,
+ writable: true, // Make writable because getters/setters can be mocked
+ });
+ }
+
+ /**
+ * Add a trackable method to a mock object. A trackable method is one that can
+ * be verified using `mock.calls[someMethod]`.
+ *
+ * @param original - The original object containing the method to add.
+ * @param mock - The mock object receiving the method.
+ * @param method - The name of the method.
+ */
+ #addTrackableMethodToMockObject(
+ original: ClassToMock,
+ mock: Mock,
+ method: keyof ClassToMock,
+ ): void {
+ Object.defineProperty(mock, method, {
+ value: (...args: unknown[]) => {
+ // Track that this method was called
+ mock.calls[method]++;
+
+ // Make sure the method calls its original self
+ const methodToCall =
+ (original[method as keyof ClassToMock] as unknown as (
+ ...params: unknown[]
+ ) => unknown);
+
+ // We need to check if the method was pre-preprogrammed to return
+ // something. If it was, then we make sure that this method we are
+ // currently defining returns that pre-programmed value.
+ if (methodToCall instanceof PreProgrammedMethod) {
+ return methodToCall.return_value;
+ }
+
+ // When method calls its original self, let the `this` context of the
+ // original be the mock. Reason being the mock has tracking and the
+ // original does not.
+ const bound = methodToCall.bind(mock);
+
+ return bound(...args);
+ },
+ });
+ }
+
/**
* Get all properties--public, protected, private--from the object that will
* be mocked.
@@ -131,7 +223,7 @@ export class MockBuilder {
*
* @returns An array of the object's properties.
*/
- protected getAllProperties(obj: T): string[] {
+ #getAllPropertyNames(obj: ClassToMock): string[] {
let functions: string[] = [];
let clone = obj;
do {
@@ -141,7 +233,7 @@ export class MockBuilder {
return functions.sort().filter(
function (e: string, i: number, arr: unknown[]) {
if (
- e != arr[i + 1] && typeof obj[e as keyof T] != "function"
+ e != arr[i + 1] && typeof obj[e as keyof ClassToMock] != "function"
) {
return true;
}
@@ -157,7 +249,7 @@ export class MockBuilder {
*
* @returns An array of the object's functions.
*/
- protected getAllFunctions(obj: T): string[] {
+ #getAllFunctionNames(obj: ClassToMock): string[] {
let functions: string[] = [];
let clone = obj;
do {
@@ -167,7 +259,7 @@ export class MockBuilder {
return functions.sort().filter(
function (e: string, i: number, arr: unknown[]) {
if (
- e != arr[i + 1] && typeof obj[e as keyof T] == "function"
+ e != arr[i + 1] && typeof obj[e as keyof ClassToMock] == "function"
) {
return true;
}
diff --git a/src/pre_programmed_method.ts b/src/pre_programmed_method.ts
new file mode 100644
index 00000000..b55a1de4
--- /dev/null
+++ b/src/pre_programmed_method.ts
@@ -0,0 +1,51 @@
+import type { MethodOf } from "./types.ts";
+
+class PreProgrammedMethodError extends Error {}
+
+/**
+ * Class that allows to be a "stand-in" for a method. For example, when used in a mock object, the mock object can replace methods with pre-programmed methods (using this class), and have a system under test use the pre-programmed methods.
+ */
+export class PreProgrammedMethod {
+ #method_name: MethodOf;
+ #return_value?: MethodReturnValue;
+ public is_pre_programmed = true;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // FILE MARKER - CONSTRUCTOR /////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @param methodName - The name of the method to program. Must be a method of
+ * the original object in question.
+ */
+ constructor(methodName: MethodOf) {
+ this.#method_name = methodName;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // FILE MARKER - GETTERS / SETTERS //////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+
+ get return_value(): MethodReturnValue {
+ if (this.#return_value === undefined) {
+ throw new PreProgrammedMethodError(
+ `Pre-programmed method "${this.#method_name}" does not have a return value.`,
+ );
+ }
+
+ return this.#return_value;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // FILE MARKER - METHODS - PUBLIC ///////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Pre-program this method to return the given value.
+ * @param returnValue The value that should be returned when this object is
+ * being used in place of an original method.
+ */
+ public willReturn(returnValue: MethodReturnValue): void {
+ this.#return_value = returnValue;
+ }
+}
diff --git a/src/types.ts b/src/types.ts
index 4c6125a6..0bd77f1d 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,18 +1,17 @@
-export type TConstructorFunction = {
- new (...args: unknown[]): T;
- [key: string]: unknown;
-};
-
// deno-lint-ignore no-explicit-any
export type Constructor = new (...args: any[]) => T;
-export type Mocked = T & {
- calls: { [k in keyof T]: T[k] extends () => void ? number : never };
- is_mock: true;
-};
+export type MockedObject = { [k: string]: unknown };
export type Stubbed = T & {
calls: { [k in keyof T]?: T[k] extends () => void ? number : never };
stub: (p: string, v: unknown) => void;
is_stubbed: true;
};
+
+export type MethodCalls