udibo / mock Goto Github PK
View Code? Open in Web Editor NEWUtilities to help mock behavior, spy on function calls, stub methods, and fake time for tests.
License: MIT License
Utilities to help mock behavior, spy on function calls, stub methods, and fake time for tests.
License: MIT License
Reporting here as the failing dependency causes this issue.
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);
}
});
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.
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);
}
});
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.
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.
In many places in this module, I use any with lint ignore when I should use unknown as the type instead.
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.
Lines 337 to 342 in cf21e99
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
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
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.
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;
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.
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 would one go about stubbing methods called in the Deno
namespace?
e.g. running a cli task:
const run = stub(Deno, "run");
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.
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.
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.
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?
Current resolvesNext only takes AsyncIterables. It should be changed to be able to also take iterables so that users won't need to manually convert their iterables into async iterables.
This has left me stumped, but somehow, after upgrading to v0.12.0, mocking fetch
on globalThis
seems to fail. See failing test run โ https://github.com/getoslash/chrome-webstore-cli/runs/4287269860?check_suite_focus=true
You can see that on the previous versions, the tests pass fine.
One failing test โ https://github.com/getoslash/chrome-webstore-cli/blob/26e037061f286f6848240368e86acd3036d4dd62/tests/helpers.api.test.ts#L171-L309
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.
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.
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.
Lines 303 to 360 in a22be7f
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
Currently ExpectedSpyCall is only exported from asserts.ts
. Add test coverage for verifying exported properties from mod.ts.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.