Giter Site home page Giter Site logo

udibo / mock Goto Github PK

View Code? Open in Web Editor NEW
30.0 30.0 2.0 283 KB

Utilities to help mock behavior, spy on function calls, stub methods, and fake time for tests.

License: MIT License

TypeScript 100.00%
deno faketime javascript mock spy stub typescript

mock's People

Contributors

jhechtf avatar kylejune avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

zhangaz1 jhechtf

mock's Issues

(FakeTimer): No way to sense microtasks like sinonjs/fake-timers

No methods like clock.runMicrotasks() in https://github.com/sinonjs/fake-timers.

I cannot test like following.

import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts#^";
import type { Spy } from "https://deno.land/x/[email protected]/spy.ts";
import { spy } from "https://deno.land/x/[email protected]/spy.ts";
import { FakeTime } from "https://deno.land/x/[email protected]/time.ts";

const wait = (ms: number) => {
  return new Promise((resolve, reject) => {
    if (ms > 10000) return reject(new Error("Can't wait so long!!!"));
    setTimeout(resolve, ms);
  });
};

Deno.test("wait", () => {
  const time: FakeTime = new FakeTime();

  try {
    main();
  } finally {
    time.restore();
  }

  function main() {
    const ok: Spy<void> = spy();
    const ng: Spy<void> = spy();
    wait(500).then(ok).catch(ng);

    assertEquals(ok.calls.length, 0);
    assertEquals(ng.calls.length, 0);

    time.tick(1000);
    // no way to consume all microtaks!

    assertEquals(ok.calls.length, 1); // FAIL
    assertEquals(ng.calls.length, 0);
  }
});

Workaround

I'm using following as trigger. Or is it correct way to do so?

// ...
  const nativeSetTimeout = setTimeout;
  const runMicrotasks = () => {
    return new Promise((resolve) => nativeSetTimeout(resolve, 0));
  };
// ...

Note that await 0; is not enough because it only consumes currently queued microtasks.

full
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts#^";
import type { Spy } from "https://deno.land/x/[email protected]/spy.ts";
import { spy } from "https://deno.land/x/[email protected]/spy.ts";
import { FakeTime } from "https://deno.land/x/[email protected]/time.ts";

const wait = (ms: number) => {
  return new Promise((resolve, reject) => {
    if (ms > 10000) return reject(new Error("Can't wait so long!!!"));
    setTimeout(resolve, ms);
  });
};

Deno.test("wait", async () => {
  const nativeSetTimeout = setTimeout;
  const runMicrotasks = () => {
    return new Promise((resolve) => nativeSetTimeout(resolve, 0));
  };
  const time: FakeTime = new FakeTime();

  try {
    await main();
  } finally {
    time.restore();
  }

  async function main() {
    const ok: Spy<void> = spy();
    const ng: Spy<void> = spy();
    wait(500).then(ok).catch(ng);

    assertEquals(ok.calls.length, 0);
    assertEquals(ng.calls.length, 0);

    time.tick(1000);
    await runMicrotasks();

    assertEquals(ok.calls.length, 1);
    assertEquals(ng.calls.length, 0);
  }
});

Allow resolvesNext to continue after rejections in an array

#1 (comment)

The call resolvesNext([Promise.reject("oops"), 5]) will return a function that rejects with "oops" then resolves undefined for each call afterward. Users would expect it to resolve the next value in the array after the rejection. This would make it easier to test error handling of asynchronous functions without having to write your own stub function.

Date, Timeouts, and Intervals

I plan on adding the ability to fake time. With fake time you will be able to set a starting time and control when or how time ticks forward. When time is faked, Date, setTimeout, clearTimeout, setInterval, clearInterval would all be faked on the window object until time.restore() is called.

Replace any with unknown

In many places in this module, I use any with lint ignore when I should use unknown as the type instead.

Potential bug in FakeTime.restoreFor

It is possible for previously queued code to execute between the restore and override steps in restoreFor, which means they unexpectedly use the native time functions.

mock/time.ts

Lines 337 to 342 in cf21e99

time.restoreGlobals();
try {
result = await callback.apply(null, args);
} finally {
time.overrideGlobals();
}

I found this when using delay (which in turn uses restoreFor).

import { FakeTime, NativeTime, delay } from "https://deno.land/x/[email protected]/time.ts";

function isNative(prefix) {
  console.log(prefix, setTimeout === NativeTime.setTimeout ? "native" : "not native");
}

async function fun() {
  await Promise.resolve();
  // I expect setTimeout to be fake here but it is not
  isNative("fun");
}

const ft = new FakeTime();
isNative("before");
fun();
await delay(0);
isNative("after");

// outputs:
// before not native
// fun native
// after not native

stub function instead of object

possible to stub a function directly instead of passing in an instance of the function where it belongs to?

reason being is because i'm moving towards functional programming and would like to do away with classes for implementation

Remove assertPassthrough

I created this initially for testing that all the Date functions are spied on correctly. I think it would be better to just test each of those functions to verifying they are working correctly. The new tests don't need to verify that every possible argument combination works, it just needs to verify that a call to the fake Date's methods pass through to the real Date methods with the arguments provided.

Test blocks, Setup, and Teardown

The built-in deno test runner has Deno.test for registering tests but the only way to organize tests is by file.

My proposal is to add a few functions for helping with registering tests and grouping them together. test would just be a wrapper for Deno.test that has an additional focus option. The before and after functions would be able to be used at the start of describe blocks only, throwing an error if used elsewhere. The test definition for the describe block would be used as the default test definition for all tests within it.

interface TestDefinition extends Deno.TestDefinition {
  focus?: boolean,
}
describe(options: TestDefinition): void;
test(options: TestDefinition): void;
beforeEach(() => void | Promise<void>): void;
beforeAll(() => void | Promise<void>): void;
afterEach(() => void | Promise<void>): void;
afterAll(() => void | Promise<void>): void;

Simplify restoring all functions for a test

Currently for each spy or stub on an instance method, you need to manually call restore on them to restore the original function. The test code needs to be in a try block and the restore calls need to be in a finally block to ensure the instance methods are always restored before the tests exit.

My proposal is to add 3 new functions. I'm open to suggestions on better names for these functions. I think the function name mock might cause some confusion.

  • startMock: void
    • This creates a checkpoint to restore to. We would need to keep track of all the functions that are stubbed or mocked from the checkpoint on so that when restore is called, it can call restore on each of the mocks created after this function was called.
  • restore(): void
    • This would cleanup all mocks created since the last startMock call. This would make it possible to restore just the mocks in the test while leaving mocks created before the last startMock call in place.
  • mockFor(fn: () => void | Promise<void>): () => void | Promise<void>
    • This function will just have a try/finally, where startMock is called, then the test callback funtion is called in a try block, then restore is called in a finally block. For all your test functions that have mocks, you could simply use mockFor to wrap them.

Request for a new release with the mod.ts file

It looks like deno.land/x just changed the default version (if none was specified) from the master branch to the latest release. I'd love to continue using the mod.ts file in the master branch, but it looks like it's not in a release yet.

Thanks for making this! And isn't Deno great!?

How to stub Deno namespace

How would one go about stubbing methods called in the Deno namespace?

e.g. running a cli task:

const run = stub(Deno, "run");

Add runMicrotasks to FakeTime

This function should just return delay(0) so that all microtasks that are currently in the queue will be called before continuing. If those microtasks queue additional microtasks after runMicrotasks is called, they will not get called by this. This gets rid of the need to export the delay function from mod.ts. The delay function was originally added to help with testing that time.ts is working correctly.

This feature request is based on the discussion from issue #14.

Add tickAsync

Currently tick just increments time.now by the tick ms amount. time.now will call each timer that is activated by the advance in time. Those callbacks are called synchronously until there are no more. If those queued callbacks add additional tasks to the queue, they are not called. Add a test case demonstrating this limitation to the tick function.

After adding a test case for that limitation to tick, add a tickAsync function that will call all callbacks with awaiting time.runMicrotasks before each one. That will ensure microtasks are run before timers. At the end it should await runMicrotasks to ensure any microtasks added by the timer callbacks that were run are executed before continuing.

This feature request is based on the discussion from issue #14.

Design and implement a mock for fetch

Fetch is a commonly used global function that users would want to be able to mock. Currently they would have to use stub and design their own replacement fetch function.

I'd like to design a fakeFetch function to make it easier for users to stub in a more standard way. I haven't fleshed out a design yet for this and am open to contributions. If you'd like to contribute a feature like this, first comment on this issue with information about your proposed design for a fakeFetch function. If I approve of it, I'd be happy to have it included with the mock module. If you do not want to implement the idea, I'd be happy to when I have free time to or if I reach a point where I need to stub fetch for another project I'm working on.

v1.0.0

So far I have spys and stubs. I currently plan on adding a way to fake time for Date, setTimeout/clearTimeout, and setInterval/clearInterval.

Does anyone have any ideas for things that should be added or changed before tagging v1.0.0?

Stub should work on non function properties

Currently if you stub a non function property on an object, we will spy on the getter and setter but not replace the value with the stub value. As a result of this issue, the stubbed property will return the original value rather than the stub. To correct this issue, the spy's getter should return the spy if it has a func or the value if it does not.

Stop spy and stub working for non function values

Currently spy and stub work for instance properties but not very well. It does it by creating a spy getter and setter for the property. Getting rid of that will make the Spy interface cleaner. It would still be possible to create spys and stubs for getters and setters, it you would just have to be more explicit and use Object.defineProperty to set the getters and setters to be spied on.

With spying, if the getters and setters are actually on the prototype instead of the instance passed to spy, they wouldn't call the original getter and setters from the prototype. This could cause behavior to be different. I believe what would be needed to make it work the way one would expect it to would require traversing the hierarchy to find the actual PropertyDescriptor.

It would also be important to match the behavior. The enumerable behavior needs to match what it would be if you were not spying. If the property is not writable, the spy needs to also not be writable. All of this needs to have good test coverage.

Delete behavior would need to be verified as well to be able to add this back.

I think if a shorthand is added back for spying on properties with getters and setters, it would be best to create new spy and stub interfaces and functions specific to properties. Like PropertySpy and propertySpy.

Improve test coverage for time.ts

I removed the last 2 tests here because they relied on system time and would intermittently fail. Need to figure out a better way to test FakeTime.

mock/time_test.ts

Lines 303 to 360 in a22be7f

Deno.test("FakeTime ticks forward when advanceRate is set", async () => {
let time: FakeTime = new FakeTime(null, { advanceRate: 1 });
let start: number = Date.now();
try {
assertEquals(Date.now(), start);
await delay(11);
assertEquals(Date.now(), start + 10);
await delay(6);
assertEquals(Date.now(), start + 10);
await delay(16);
assertEquals(Date.now(), start + 30);
time.restore();
time = new FakeTime(null, { advanceRate: 1000 });
start = Date.now();
assertEquals(Date.now(), start);
await delay(11);
assertEquals(Date.now(), start + 10000);
await delay(6);
assertEquals(Date.now(), start + 10000);
await delay(16);
assertEquals(Date.now(), start + 30000);
} finally {
time.restore();
}
});
Deno.test("FakeTime ticks forward at advanceFrequency when advanceRate is set", async () => {
let time: FakeTime = new FakeTime(
null,
{ advanceRate: 1, advanceFrequency: 15 },
);
let start: number = Date.now();
try {
assertEquals(Date.now(), start);
await delay(16);
assertEquals(Date.now(), start + 15);
await delay(11);
assertEquals(Date.now(), start + 15);
await delay(21);
assertEquals(Date.now(), start + 45);
time.restore();
time = new FakeTime(null, { advanceRate: 1000, advanceFrequency: 15 });
start = Date.now();
assertEquals(Date.now(), start);
await delay(16);
assertEquals(Date.now(), start + 15000);
await delay(11);
assertEquals(Date.now(), start + 15000);
await delay(21);
assertEquals(Date.now(), start + 45000);
} finally {
time.restore();
}
});

Fix time type error with dom lib compiler options

When using dom compiler options, the type of the first argument is TimerHandler. When overriding the setTimeout and setInterval functions on globalThis, it gets a type error. TimerHandler is an alias for Function | string, but the timer only allows a callback as the first argument. The string input is not recommended or implemented in Deno, so I did not implement it in this library either.

https://developer.mozilla.org/en-US/docs/Web/API/setTimeout

Here are the type errors that would occur when importing this module into one that uses dom compiler options.

error: TS2322 [ERROR]: Type '(callback: (...args: any[]) => void, delay?: number, ...args: any[]) => number' is not assignable to type '(handler: TimerHandler, timeout?: number | undefined, ...arguments: any[]) => number'.
  Types of parameters 'callback' and 'handler' are incompatible.
    Type 'TimerHandler' is not assignable to type '(...args: any[]) => void'.
      Type 'string' is not assignable to type '(...args: any[]) => void'.
    globalThis.setTimeout = FakeTime.setTimeout;
    ~~~~~~~~~~~~~~~~~~~~~
    at file:///home/kyle/Projects/deno/mock/time.ts:271:5

TS2322 [ERROR]: Type '(callback: (...args: any[]) => unknown, delay?: number, ...args: any[]) => number' is not assignable to type '(handler: TimerHandler, timeout?: number | undefined, ...arguments: any[]) => number'.
  Types of parameters 'callback' and 'handler' are incompatible.
    Type 'TimerHandler' is not assignable to type '(...args: any[]) => unknown'.
      Type 'string' is not assignable to type '(...args: any[]) => unknown'.
    globalThis.setInterval = FakeTime.setInterval;
    ~~~~~~~~~~~~~~~~~~~~~~
    at file:///home/kyle/Projects/deno/mock/time.ts:273:5

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.