Skip to content

Commit 15d35ac

Browse files
leon19johanblumenberg
authored andcommitted
feat(matchers): add Date matchers
* Added `isBefore()` matcher * Added `isAfter()` matcher * Added `isBeforeOrEqual()` matcher * Added `isAfterOrEqual()` matcher These matchers can be used to stub methods or easily verify calls without having to capture the method call ```ts class User { updatedAt: Date; } class UserRepository { saveOne(user: User): Promise<void> { // update the user in the database } } class UserService { constructor(private readonly userRepository: UserRespository) {} save(user: User): Promise<User> { user.updatedAt = new Date(); await this.userRepository.saveOne(user); } } const userRepository = mock(UserRepository); const userService = new UserService(instance(userRepository)); when(userRepository.saveOne(anything())).thenResolve(); const now = new Date(); await userService.save(new User()); verify(userRepository.saveOne(objectContaining({ updatedAt: isAfterOrEqual(now) }))).once(); ```
1 parent 64a1618 commit 15d35ac

12 files changed

+9609
-21
lines changed

package-lock.json

Lines changed: 8947 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"karma-typescript": "^5.2.0",
4747
"karma-typescript-preprocessor": "^0.4.0",
4848
"puppeteer": "^5.5.0",
49+
"ts-expect": "^1.3.0",
4950
"ts-helpers": "^1.1.2",
5051
"ts-jest": "^26.0.0",
5152
"tslint": "^5.7.0",
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {Matcher} from "../Matcher";
2+
3+
export abstract class DateMatcher extends Matcher {
4+
protected readonly date: Date;
5+
6+
constructor(date: MaybeDate) {
7+
super();
8+
9+
this.date = new Date(date);
10+
11+
if (!isValidDate(this.date)) {
12+
throw new InvalidDateError(date);
13+
}
14+
}
15+
}
16+
17+
export class InvalidDateError extends Error {
18+
public readonly name = this.constructor.name;
19+
20+
constructor(date: MaybeDate) {
21+
super(`Invalid date: ${date}`);
22+
23+
Object.setPrototypeOf(this, InvalidDateError.prototype);
24+
}
25+
}
26+
27+
export type MaybeDate = number | string | Date;
28+
29+
/** @internal */
30+
export function isValidDate(date: Date): date is Date {
31+
return !Number.isNaN(date.getTime());
32+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {DateMatcher, isValidDate, MaybeDate} from "./DateMatcher";
2+
3+
export class IsAfterMatcher extends DateMatcher {
4+
public match(value: MaybeDate): boolean {
5+
value = new Date(value);
6+
7+
return isValidDate(value) && value.getTime() > this.date.getTime();
8+
}
9+
10+
public toString(): string {
11+
return `isAfter(${this.date.toISOString()})`;
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {DateMatcher, isValidDate, MaybeDate} from "./DateMatcher";
2+
3+
export class IsAfterOrEqualMatcher extends DateMatcher {
4+
public match(value: MaybeDate): boolean {
5+
value = new Date(value);
6+
7+
return isValidDate(value) && value.getTime() >= this.date.getTime();
8+
}
9+
10+
public toString(): string {
11+
return `isAfterOrEqual(${this.date.toISOString()})`;
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {DateMatcher, isValidDate, MaybeDate} from "./DateMatcher";
2+
3+
export class IsBeforeMatcher extends DateMatcher {
4+
public match(value: MaybeDate): boolean {
5+
value = new Date(value);
6+
7+
return isValidDate(value) && value.getTime() < this.date.getTime();
8+
}
9+
10+
public toString(): string {
11+
return `isBefore(${this.date.toISOString()})`;
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {DateMatcher, isValidDate, MaybeDate} from "./DateMatcher";
2+
3+
export class IsBeforeOrEqualMatcher extends DateMatcher {
4+
public match(value: MaybeDate): boolean {
5+
value = new Date(value);
6+
7+
return isValidDate(value) && value.getTime() <= this.date.getTime();
8+
}
9+
10+
public toString(): string {
11+
return `isBeforeOrEqual(${this.date.toISOString()})`;
12+
}
13+
}

src/ts-mockito.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ import {AnyOfClassMatcher} from "./matcher/type/AnyOfClassMatcher";
1717
import {AnyStringMatcher} from "./matcher/type/AnyStringMatcher";
1818
import {AnythingMatcher} from "./matcher/type/AnythingMatcher";
1919
import {BetweenMatcher} from "./matcher/type/BetweenMatcher";
20+
import {MaybeDate} from "./matcher/type/date/DateMatcher";
21+
import {IsAfterMatcher} from "./matcher/type/date/IsAfterMatcher";
22+
import {IsAfterOrEqualMatcher} from "./matcher/type/date/IsAfterOrEqualMatcher";
23+
import {IsBeforeMatcher} from "./matcher/type/date/IsBeforeMatcher";
24+
import {IsBeforeOrEqualMatcher} from "./matcher/type/date/IsBeforeOrEqualMatcher";
2025
import {DeepEqualMatcher} from "./matcher/type/DeepEqualMatcher";
2126
import {EndsWithMatcher} from "./matcher/type/EndsWithMatcher";
2227
import {MatchingStringMatcher} from "./matcher/type/MatchingStringMatcher";
@@ -197,6 +202,22 @@ export function nextTick(): Promise<void> {
197202
return new Promise(resolve => originalSetTimeout(() => originalSetImmediate(resolve), 0));
198203
}
199204

205+
export function isAfter(date: MaybeDate): Date {
206+
return new IsAfterMatcher(date) as any;
207+
}
208+
209+
export function isAfterOrEqual(date: MaybeDate): Date {
210+
return new IsAfterOrEqualMatcher(date) as any;
211+
}
212+
213+
export function isBefore(date: MaybeDate): Date {
214+
return new IsBeforeMatcher(date) as any;
215+
}
216+
217+
export function isBeforeOrEqual(date: MaybeDate): Date {
218+
return new IsBeforeOrEqualMatcher(date) as any;
219+
}
220+
200221
// Export default object with all members (ember-browserify doesn't support named exports).
201222
export default {
202223
spy,
@@ -226,4 +247,8 @@ export default {
226247
MockPropertyPolicy,
227248
defer,
228249
nextTick,
250+
isAfter,
251+
isAfterOrEqual,
252+
isBefore,
253+
isBeforeOrEqual,
229254
};
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import {expectType} from "ts-expect";
2+
import {InvalidDateError, MaybeDate} from "../../../../src/matcher/type/date/DateMatcher";
3+
import {IsAfterMatcher} from "../../../../src/matcher/type/date/IsAfterMatcher";
4+
import {imock, instance, isAfter, when} from "../../../../src/ts-mockito";
5+
6+
describe("IsAfterMatcher", () => {
7+
describe("isAfter()", () => {
8+
it("should be a IfAfterOrEqualMatcher instance", () => {
9+
expect(isAfter(new Date()) instanceof IsAfterMatcher).toBe(true);
10+
});
11+
12+
it("should be a date type", () => {
13+
expectType<Date>(isAfter(new Date()));
14+
});
15+
});
16+
17+
describe("#constructor()", () => {
18+
it("should accept a number", () => {
19+
expect(() => new IsAfterMatcher(0)).not.toThrow();
20+
});
21+
22+
it("should accept a string", () => {
23+
expect(() => new IsAfterMatcher("2020-01-01")).not.toThrow();
24+
});
25+
26+
it("should accept a date", () => {
27+
expect(() => new IsAfterMatcher(new Date())).not.toThrow();
28+
});
29+
30+
it("should throw an error when given value is not a valid date", () => {
31+
const date = "asdfg";
32+
expect(() => new IsAfterMatcher(date)).toThrow(new InvalidDateError(date));
33+
});
34+
});
35+
36+
describe("#match()", () => {
37+
it("should return true when the given date is after the expected date", () => {
38+
const date = "2020-01-01";
39+
40+
const matcher = new IsAfterMatcher(date);
41+
42+
const testDate = "2020-01-02";
43+
expect(matcher.match(new Date(testDate))).toBe(true);
44+
expect(matcher.match(testDate)).toBe(true);
45+
expect(matcher.match(new Date(testDate).getTime())).toBe(true);
46+
});
47+
48+
it("should return false when the given date is equal to the expected date", () => {
49+
const date = "2020-01-01";
50+
51+
const matcher = new IsAfterMatcher(date);
52+
53+
const testDate = "2020-01-01";
54+
expect(matcher.match(new Date(testDate))).toBe(false);
55+
expect(matcher.match(testDate)).toBe(false);
56+
expect(matcher.match(new Date(testDate).getTime())).toBe(false);
57+
});
58+
59+
it("should return false when the given date is before the expected date", () => {
60+
const date = "2020-01-02";
61+
62+
const matcher = new IsAfterMatcher(date);
63+
64+
const testDate = "2020-01-01";
65+
expect(matcher.match(new Date(testDate))).toBe(false);
66+
expect(matcher.match(testDate)).toBe(false);
67+
expect(matcher.match(new Date(testDate).getTime())).toBe(false);
68+
});
69+
70+
it("should return false when the given date is an invalid date", () => {
71+
const matcher = new IsAfterMatcher(new Date());
72+
73+
expect(matcher.match(new Date("Invalid Date"))).toBe(false);
74+
});
75+
});
76+
77+
describe("#toString()", () => {
78+
it("should correctly get the string representation", () => {
79+
const date = new Date();
80+
81+
expect(new IsAfterMatcher(date).toString()).toBe(`isAfter(${date.toISOString()})`);
82+
});
83+
});
84+
85+
describe("stubbing a method", () => {
86+
let testServiceMock: TestService;
87+
let testService: TestService;
88+
89+
beforeEach(() => {
90+
testServiceMock = imock();
91+
testService = instance(testServiceMock);
92+
});
93+
94+
it("should pass the verification when the given date is after the expected date", () => {
95+
const date = new Date("2020-01-01");
96+
97+
when(testServiceMock.testMethod(isAfter(date))).thenReturn(true);
98+
99+
const result = testService.testMethod(new Date("2020-01-02"));
100+
101+
expect(result).toBe(true);
102+
});
103+
104+
it("should not pass the verification when the given date is equal to the expected date", () => {
105+
const date = new Date("2020-01-01");
106+
107+
when(testServiceMock.testMethod(isAfter(date))).thenReturn(true);
108+
109+
const result = testService.testMethod(new Date("2020-01-01"));
110+
111+
expect(result).toBeNull();
112+
});
113+
114+
it("should not pass the verification when the given date is before the expected date", () => {
115+
const date = new Date("2020-01-02");
116+
117+
when(testServiceMock.testMethod(isAfter(date))).thenReturn(true);
118+
119+
const result = testService.testMethod(new Date("2020-01-01"));
120+
121+
expect(result).toBeNull();
122+
});
123+
124+
it("should not pass the verification false when the given date is an invalid date", () => {
125+
const date = new Date("2020-01-01");
126+
127+
when(testServiceMock.testMethod(isAfter(date))).thenReturn(true);
128+
129+
const result = testService.testMethod(new Date("Invalid Date"));
130+
131+
expect(result).toBeNull();
132+
});
133+
});
134+
});
135+
136+
interface TestService {
137+
testMethod(date: MaybeDate): boolean;
138+
}

0 commit comments

Comments
 (0)