mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
feat(test): toHaveBeenCalledWith and toHaveBeenLastCalledWith (#7277)
This commit is contained in:
@@ -32,7 +32,6 @@ Some notable missing features:
|
||||
|
||||
- `expect.extend()`
|
||||
- `expect().toMatchInlineSnapshot()`
|
||||
- `expect().toHaveBeenCalledWith()`
|
||||
- `expect().toHaveReturned()`
|
||||
|
||||
---
|
||||
|
||||
@@ -313,12 +313,12 @@ Bun implements the following matchers. Full Jest compatibility is on the roadmap
|
||||
|
||||
---
|
||||
|
||||
- ❌
|
||||
- ✅
|
||||
- [`.toHaveBeenCalledWith()`](https://jestjs.io/docs/expect#tohavebeencalledwitharg1-arg2-)
|
||||
|
||||
---
|
||||
|
||||
- ❌
|
||||
- ✅
|
||||
- [`.toHaveBeenLastCalledWith()`](https://jestjs.io/docs/expect#tohavebeenlastcalledwitharg1-arg2-)
|
||||
|
||||
---
|
||||
|
||||
6
packages/bun-types/bun-test.d.ts
vendored
6
packages/bun-types/bun-test.d.ts
vendored
@@ -1144,7 +1144,11 @@ declare module "bun:test" {
|
||||
/**
|
||||
* Ensure that a mock function is called with specific arguments.
|
||||
*/
|
||||
// toHaveBeenCalledWith(...expected: Array<unknown>): void;
|
||||
toHaveBeenCalledWith(...expected: Array<unknown>): void;
|
||||
/**
|
||||
* Ensure that a mock function is called with specific arguments for the last call.
|
||||
*/
|
||||
toHaveBeenLastCalledWith(...expected: Array<unknown>): void;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3493,8 +3493,137 @@ pub const Expect = struct {
|
||||
return .zero;
|
||||
}
|
||||
|
||||
pub const toHaveBeenCalledWith = notImplementedJSCFn;
|
||||
pub const toHaveBeenLastCalledWith = notImplementedJSCFn;
|
||||
pub fn toHaveBeenCalledWith(this: *Expect, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
|
||||
JSC.markBinding(@src());
|
||||
|
||||
const thisValue = callframe.this();
|
||||
const arguments_ = callframe.argumentsPtr()[0..callframe.argumentsCount()];
|
||||
const arguments: []const JSValue = arguments_.ptr[0..arguments_.len];
|
||||
defer this.postMatch(globalObject);
|
||||
const value: JSValue = this.getValue(globalObject, thisValue, "toHaveBeenCalledWith", "<green>expected<r>") orelse return .zero;
|
||||
|
||||
active_test_expectation_counter.actual += 1;
|
||||
|
||||
const calls = JSMockFunction__getCalls(value);
|
||||
|
||||
if (calls == .zero or !calls.jsType().isArray()) {
|
||||
globalObject.throw("Expected value must be a mock function: {}", .{value});
|
||||
return .zero;
|
||||
}
|
||||
|
||||
var pass = false;
|
||||
|
||||
if (calls.getLength(globalObject) > 0) {
|
||||
var itr = calls.arrayIterator(globalObject);
|
||||
while (itr.next()) |callItem| {
|
||||
if (callItem == .zero or !callItem.jsType().isArray()) {
|
||||
globalObject.throw("Expected value must be a mock function with calls: {}", .{value});
|
||||
return .zero;
|
||||
}
|
||||
|
||||
if (callItem.getLength(globalObject) != arguments.len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var callItr = callItem.arrayIterator(globalObject);
|
||||
var match = true;
|
||||
while (callItr.next()) |callArg| {
|
||||
if (!callArg.jestDeepEquals(arguments[callItr.i - 1], globalObject)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
pass = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
if (pass) return thisValue;
|
||||
|
||||
// handle failure
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toHaveBeenCalledWith", "<green>expected<r>", true);
|
||||
const fmt = signature ++ "\n\n" ++ "Number of calls: <red>{any}<r>\n";
|
||||
globalObject.throwPretty(fmt, .{calls.getLength(globalObject)});
|
||||
return .zero;
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toHaveBeenCalledWith", "<green>expected<r>", false);
|
||||
const fmt = signature ++ "\n\n" ++ "Number of calls: <red>{any}<r>\n";
|
||||
globalObject.throwPretty(fmt, .{calls.getLength(globalObject)});
|
||||
return .zero;
|
||||
}
|
||||
|
||||
pub fn toHaveBeenLastCalledWith(this: *Expect, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
|
||||
JSC.markBinding(@src());
|
||||
|
||||
const thisValue = callframe.this();
|
||||
const arguments_ = callframe.argumentsPtr()[0..callframe.argumentsCount()];
|
||||
const arguments: []const JSValue = arguments_.ptr[0..arguments_.len];
|
||||
defer this.postMatch(globalObject);
|
||||
const value: JSValue = this.getValue(globalObject, thisValue, "toHaveBeenLastCalledWith", "<green>expected<r>") orelse return .zero;
|
||||
|
||||
active_test_expectation_counter.actual += 1;
|
||||
|
||||
const calls = JSMockFunction__getCalls(value);
|
||||
|
||||
if (calls == .zero or !calls.jsType().isArray()) {
|
||||
globalObject.throw("Expected value must be a mock function: {}", .{value});
|
||||
return .zero;
|
||||
}
|
||||
|
||||
const totalCalls = @as(u32, @intCast(calls.getLength(globalObject)));
|
||||
var lastCallValue: JSValue = .zero;
|
||||
|
||||
var pass = totalCalls > 0;
|
||||
|
||||
if (pass) {
|
||||
lastCallValue = calls.getIndex(globalObject, totalCalls - 1);
|
||||
|
||||
if (lastCallValue == .zero or !lastCallValue.jsType().isArray()) {
|
||||
globalObject.throw("Expected value must be a mock function with calls: {}", .{value});
|
||||
return .zero;
|
||||
}
|
||||
|
||||
if (lastCallValue.getLength(globalObject) != arguments.len) {
|
||||
pass = false;
|
||||
} else {
|
||||
var itr = lastCallValue.arrayIterator(globalObject);
|
||||
while (itr.next()) |callArg| {
|
||||
if (!callArg.jestDeepEquals(arguments[itr.i - 1], globalObject)) {
|
||||
pass = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
if (pass) return thisValue;
|
||||
|
||||
// handle failure
|
||||
var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true };
|
||||
const received_fmt = lastCallValue.toFmt(globalObject, &formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toHaveBeenLastCalledWith", "<green>expected<r>", true);
|
||||
const fmt = signature ++ "\n\n" ++ "Received: <red>{any}<r>" ++ "\n\n" ++ "Number of calls: <red>{any}<r>\n";
|
||||
globalObject.throwPretty(fmt, .{ received_fmt, totalCalls });
|
||||
return .zero;
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toHaveBeenLastCalledWith", "<green>expected<r>", false);
|
||||
const fmt = signature ++ "\n\n" ++ "Received: <red>{any}<r>" ++ "\n\n" ++ "Number of calls: <red>{any}<r>\n";
|
||||
globalObject.throwPretty(fmt, .{ received_fmt, totalCalls });
|
||||
return .zero;
|
||||
}
|
||||
|
||||
pub const toHaveBeenNthCalledWith = notImplementedJSCFn;
|
||||
pub const toHaveReturnedTimes = notImplementedJSCFn;
|
||||
pub const toHaveReturnedWith = notImplementedJSCFn;
|
||||
|
||||
@@ -146,11 +146,9 @@ export default [
|
||||
},
|
||||
toHaveBeenCalledWith: {
|
||||
fn: "toHaveBeenCalledWith",
|
||||
length: 1,
|
||||
},
|
||||
toHaveBeenLastCalledWith: {
|
||||
fn: "toHaveBeenLastCalledWith",
|
||||
length: 1,
|
||||
},
|
||||
toHaveBeenNthCalledWith: {
|
||||
fn: "toHaveBeenNthCalledWith",
|
||||
|
||||
@@ -65,10 +65,13 @@ describe("mock()", () => {
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(fn.mock.calls).toHaveLength(1);
|
||||
expect(fn.mock.calls[0]).toBeEmpty();
|
||||
expect(fn).toHaveBeenLastCalledWith();
|
||||
expect(fn()).toBe(42);
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
expect(fn.mock.calls).toHaveLength(2);
|
||||
expect(fn.mock.calls[1]).toBeEmpty();
|
||||
expect(fn).toHaveBeenLastCalledWith();
|
||||
expect(fn).toHaveBeenCalledWith();
|
||||
});
|
||||
test("passes this value", () => {
|
||||
const fn = jest.fn(function hey() {
|
||||
@@ -107,10 +110,13 @@ describe("mock()", () => {
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(fn.mock.calls).toHaveLength(1);
|
||||
expect(fn.mock.calls[0]).toBeEmpty();
|
||||
expect(fn).toHaveBeenLastCalledWith();
|
||||
expect(Number(fn.call(234))).toBe(234);
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
expect(fn.mock.calls).toHaveLength(2);
|
||||
expect(fn.mock.calls[1]).toBeEmpty();
|
||||
expect(fn).toHaveBeenLastCalledWith();
|
||||
expect(fn).toHaveBeenCalledWith();
|
||||
});
|
||||
test(".apply works", function () {
|
||||
const fn = jest.fn(function hey() {
|
||||
@@ -121,10 +127,13 @@ describe("mock()", () => {
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(fn.mock.calls).toHaveLength(1);
|
||||
expect(fn.mock.calls[0]).toBeEmpty();
|
||||
expect(fn).toHaveBeenLastCalledWith();
|
||||
expect(Number(fn.apply(234))).toBe(234);
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
expect(fn.mock.calls).toHaveLength(2);
|
||||
expect(fn.mock.calls[1]).toBeEmpty();
|
||||
expect(fn).toHaveBeenLastCalledWith();
|
||||
expect(fn).toHaveBeenCalledWith();
|
||||
});
|
||||
test(".bind works", () => {
|
||||
const fn = jest.fn(function hey() {
|
||||
@@ -135,10 +144,13 @@ describe("mock()", () => {
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(fn.mock.calls).toHaveLength(1);
|
||||
expect(fn.mock.calls[0]).toBeEmpty();
|
||||
expect(fn).toHaveBeenLastCalledWith();
|
||||
expect(Number(fn.bind(234)())).toBe(234);
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
expect(fn.mock.calls).toHaveLength(2);
|
||||
expect(fn.mock.calls[1]).toBeEmpty();
|
||||
expect(fn).toHaveBeenLastCalledWith();
|
||||
expect(fn).toHaveBeenCalledWith();
|
||||
});
|
||||
test(".name works", () => {
|
||||
const fn = jest.fn(function hey() {
|
||||
@@ -181,6 +193,10 @@ describe("mock()", () => {
|
||||
value: 43,
|
||||
});
|
||||
expect(fn.mock.calls[0]).toEqual([43]);
|
||||
expect(fn).toHaveBeenCalled();
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(fn).toHaveBeenLastCalledWith(43);
|
||||
expect(fn).toHaveBeenCalledWith(43);
|
||||
});
|
||||
test("works when throwing", () => {
|
||||
const instance = new Error("foo");
|
||||
@@ -193,6 +209,8 @@ describe("mock()", () => {
|
||||
value: instance,
|
||||
});
|
||||
expect(fn.mock.calls[0]).toEqual([43]);
|
||||
expect(fn).toHaveBeenLastCalledWith(43);
|
||||
expect(fn).toHaveBeenCalledWith(43);
|
||||
});
|
||||
test("mockReset works", () => {
|
||||
const instance = new Error("foo");
|
||||
@@ -205,11 +223,15 @@ describe("mock()", () => {
|
||||
value: instance,
|
||||
});
|
||||
expect(fn.mock.calls[0]).toEqual([43]);
|
||||
expect(fn).toHaveBeenLastCalledWith(43);
|
||||
expect(fn).toHaveBeenCalledWith(43);
|
||||
fn.mockReset();
|
||||
expect(fn.mock.calls).toBeEmpty();
|
||||
expect(fn.mock.results).toBeEmpty();
|
||||
expect(fn.mock.instances).toBeEmpty();
|
||||
expect(fn).not.toHaveBeenCalled();
|
||||
expect(fn).not.toHaveBeenLastCalledWith(43);
|
||||
expect(fn).not.toHaveBeenCalledWith(43);
|
||||
expect(() => expect(fn).toHaveBeenCalled()).toThrow();
|
||||
expect(fn(43)).toBe(undefined);
|
||||
expect(fn.mock.results).toEqual([
|
||||
@@ -219,6 +241,8 @@ describe("mock()", () => {
|
||||
},
|
||||
]);
|
||||
expect(fn.mock.calls).toEqual([[43]]);
|
||||
expect(fn).toHaveBeenLastCalledWith(43);
|
||||
expect(fn).toHaveBeenCalledWith(43);
|
||||
});
|
||||
test("mockClear works", () => {
|
||||
const instance = new Error("foo");
|
||||
@@ -231,17 +255,23 @@ describe("mock()", () => {
|
||||
value: instance,
|
||||
});
|
||||
expect(fn.mock.calls[0]).toEqual([43]);
|
||||
expect(fn).toHaveBeenLastCalledWith(43);
|
||||
expect(fn).toHaveBeenCalledWith(43);
|
||||
fn.mockClear();
|
||||
expect(fn.mock.calls).toBeEmpty();
|
||||
expect(fn.mock.results).toBeEmpty();
|
||||
expect(fn.mock.instances).toBeEmpty();
|
||||
expect(fn).not.toHaveBeenCalled();
|
||||
expect(fn).not.toHaveBeenLastCalledWith(43);
|
||||
expect(fn).not.toHaveBeenCalledWith(43);
|
||||
expect(() => fn(43)).toThrow("foo");
|
||||
expect(fn.mock.results[0]).toEqual({
|
||||
type: "throw",
|
||||
value: instance,
|
||||
});
|
||||
expect(fn.mock.calls[0]).toEqual([43]);
|
||||
expect(fn).toHaveBeenLastCalledWith(43);
|
||||
expect(fn).toHaveBeenCalledWith(43);
|
||||
});
|
||||
// this is an implementation detail i don't think we *need* to support
|
||||
test("mockClear doesnt update existing object", () => {
|
||||
@@ -255,10 +285,14 @@ describe("mock()", () => {
|
||||
value: instance,
|
||||
});
|
||||
expect(fn.mock.calls[0]).toEqual([43]);
|
||||
expect(fn).toHaveBeenLastCalledWith(43);
|
||||
expect(fn).toHaveBeenCalledWith(43);
|
||||
const stolen = fn.mock;
|
||||
fn.mockClear();
|
||||
expect(stolen).not.toBe(fn.mock);
|
||||
expect(fn.mock.calls).toBeEmpty();
|
||||
expect(fn).not.toHaveBeenLastCalledWith(43);
|
||||
expect(fn).not.toHaveBeenCalledWith(43);
|
||||
expect(stolen.calls).not.toBeEmpty();
|
||||
expect(fn.mock.results).toBeEmpty();
|
||||
expect(stolen.results).not.toBeEmpty();
|
||||
@@ -271,22 +305,29 @@ describe("mock()", () => {
|
||||
value: instance,
|
||||
});
|
||||
expect(fn.mock.calls[0]).toEqual([43]);
|
||||
expect(fn).toHaveBeenLastCalledWith(43);
|
||||
expect(fn).toHaveBeenCalledWith(43);
|
||||
});
|
||||
test("multiple calls work", () => {
|
||||
const fn = jest.fn(f => f);
|
||||
expect(fn(43)).toBe(43);
|
||||
expect(fn).toHaveBeenLastCalledWith(43);
|
||||
expect(fn(44)).toBe(44);
|
||||
expect(fn).toHaveBeenLastCalledWith(44);
|
||||
expect(fn.mock.calls[0]).toEqual([43]);
|
||||
expect(fn.mock.results[0]).toEqual({
|
||||
type: "return",
|
||||
value: 43,
|
||||
});
|
||||
expect(fn.mock.calls[1]).toEqual([44]);
|
||||
expect(fn).toHaveBeenLastCalledWith(44);
|
||||
expect(fn.mock.results[1]).toEqual({
|
||||
type: "return",
|
||||
value: 44,
|
||||
});
|
||||
expect(fn.mock.contexts).toEqual([undefined, undefined]);
|
||||
expect(fn).toHaveBeenCalledWith(43);
|
||||
expect(fn).toHaveBeenCalledWith(44);
|
||||
});
|
||||
test("this arg", () => {
|
||||
const fn = jest.fn(function (add) {
|
||||
@@ -295,6 +336,8 @@ describe("mock()", () => {
|
||||
const obj = { foo: 42, fn };
|
||||
expect(obj.fn(2)).toBe(44);
|
||||
expect(fn.mock.calls[0]).toEqual([2]);
|
||||
expect(fn).toHaveBeenLastCalledWith(2);
|
||||
expect(fn).toHaveBeenCalledWith(2);
|
||||
expect(fn.mock.results[0]).toEqual({
|
||||
type: "return",
|
||||
value: 44,
|
||||
@@ -320,19 +363,26 @@ describe("mock()", () => {
|
||||
});
|
||||
const obj = { foo: 42, fn };
|
||||
expect(obj.fn(2)).toBe(44);
|
||||
expect(fn).toHaveBeenLastCalledWith(2);
|
||||
const this2 = { foo: 43 };
|
||||
expect(fn.call(this2, 2)).toBe(45);
|
||||
expect(fn).toHaveBeenLastCalledWith(2);
|
||||
const this3 = { foo: 44 };
|
||||
expect(fn.apply(this3, [2])).toBe(46);
|
||||
expect(fn).toHaveBeenLastCalledWith(2);
|
||||
const this4 = { foo: 45 };
|
||||
expect(fn.bind(this4)(3)).toBe(48);
|
||||
expect(fn).toHaveBeenLastCalledWith(3);
|
||||
const this5 = { foo: 45 };
|
||||
expect(fn.bind(this5, 2)()).toBe(47);
|
||||
expect(fn).toHaveBeenLastCalledWith(2);
|
||||
expect(fn.mock.calls[0]).toEqual([2]);
|
||||
expect(fn.mock.calls[1]).toEqual([2]);
|
||||
expect(fn.mock.calls[2]).toEqual([2]);
|
||||
expect(fn.mock.calls[3]).toEqual([3]);
|
||||
expect(fn.mock.calls[4]).toEqual([2]);
|
||||
expect(fn).toHaveBeenCalledWith(2);
|
||||
expect(fn).toHaveBeenCalledWith(3);
|
||||
expect(fn.mock.results[0]).toEqual({
|
||||
type: "return",
|
||||
value: 44,
|
||||
@@ -509,6 +559,41 @@ describe("mock()", () => {
|
||||
expect(fn1.mock.invocationCallOrder).toEqual([first, first + 3]);
|
||||
expect(fn2.mock.invocationCallOrder).toEqual([first + 1, first + 2]);
|
||||
});
|
||||
|
||||
test("toHaveBeenCalledWith, toHaveBeenLastCalledWith works", () => {
|
||||
const fn = jest.fn();
|
||||
expect(() => expect(() => {}).not.toHaveBeenLastCalledWith()).toThrow();
|
||||
expect(() => expect(() => {}).not.toHaveBeenCalledWith()).toThrow();
|
||||
expect(fn).not.toHaveBeenCalledWith();
|
||||
expect(fn).not.toHaveBeenLastCalledWith();
|
||||
fn();
|
||||
expect(fn).toHaveBeenCalledWith();
|
||||
expect(fn).toHaveBeenLastCalledWith();
|
||||
expect(fn).not.toHaveBeenCalledWith(1);
|
||||
fn(1);
|
||||
expect(fn).toHaveBeenCalledWith(1);
|
||||
expect(fn).toHaveBeenLastCalledWith(1);
|
||||
fn(1, 2, 3);
|
||||
expect(fn).not.toHaveBeenCalledWith("123");
|
||||
expect(fn).not.toHaveBeenLastCalledWith(1);
|
||||
expect(fn).not.toHaveBeenLastCalledWith(1, 2);
|
||||
expect(fn).not.toHaveBeenLastCalledWith("123");
|
||||
expect(fn).toHaveBeenLastCalledWith(1, 2, 3);
|
||||
expect(fn).not.toHaveBeenLastCalledWith(3, 2, 1);
|
||||
fn("random string");
|
||||
expect(fn).toHaveBeenCalledWith();
|
||||
expect(fn).toHaveBeenCalledWith(1);
|
||||
expect(fn).toHaveBeenCalledWith(1, 2, 3);
|
||||
expect(fn).toHaveBeenCalledWith("random string");
|
||||
expect(fn).toHaveBeenLastCalledWith("random string");
|
||||
expect(fn).toHaveBeenCalledWith(expect.stringMatching(/^random \w+$/));
|
||||
expect(fn).toHaveBeenLastCalledWith(expect.stringMatching(/^random \w+$/));
|
||||
fn(1, undefined);
|
||||
expect(fn).not.toHaveBeenLastCalledWith(1);
|
||||
expect(fn).toHaveBeenLastCalledWith(1, undefined);
|
||||
expect(fn).toHaveBeenCalledWith(1, undefined);
|
||||
expect(fn).not.toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("spyOn", () => {
|
||||
|
||||
Reference in New Issue
Block a user