Skip to content

Commit 8d105e4

Browse files
bajtosraymondfeng
authored andcommitted
feat(testlab): add a new helper deserializedFromJson
Implement a helper function to convert an object (e.g. a Model instance) to a data object representing JSON version of the input object. Specifically, properties set to a value that cannot be represented in JSON (e.g. `undefined`) are removed from the result.
1 parent d65a17f commit 8d105e4

File tree

4 files changed

+218
-0
lines changed

4 files changed

+218
-0
lines changed

packages/testlab/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Table of contents:
5353
- [givenHttpServerConfig](#givenhttpserverconfig) - Generate HTTP server config.
5454
- [httpGetAsync](#httpgetasync) - Async wrapper for HTTP GET requests.
5555
- [httpsGetAsync](#httpsgetasync) - Async wrapper for HTTPS GET requests.
56+
- [toJSON](#toJSON) - A helper to obtain JSON data representing a given object.
5657

5758
### `expect`
5859

@@ -134,6 +135,24 @@ import {httpsGetAsync} from '@loopback/testlab';
134135
const response = await httpsGetAsync('https://example.com');
135136
```
136137

138+
### `toJSON`
139+
140+
JSON encoding does not preserve properties that are undefined. As a result,
141+
`deepEqual` checks fail because the expected model value contains these
142+
undefined property values, while the actual result returned by REST API does
143+
not. Use this function to convert a model instance into a data object as
144+
returned by REST API.
145+
146+
```ts
147+
import {createClientForHandler, toJSON} from '@loopback/testlab';
148+
149+
it('gets a todo by ID', () => {
150+
return client
151+
.get(`/todos/${persistedTodo.id}`)
152+
.expect(200, toJSON(persistedTodo));
153+
});
154+
```
155+
137156
#### Test request parsing
138157

139158
Use the factory function `stubServerRequest` to create a stub request that can

packages/testlab/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from './test-sandbox';
1212
export * from './skip-travis';
1313
export * from './request';
1414
export * from './http-server-config';
15+
export * from './to-json';

packages/testlab/src/to-json.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright IBM Corp. 2018. All Rights Reserved.
2+
// Node module: @loopback/testlab
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
// Important! Date.prototype.toJSON() returns a string.
7+
export function toJSON(value: Date): string;
8+
9+
// Important! Functions cannot be encoded in JSON.
10+
export function toJSON(value: Function): undefined;
11+
12+
// Distinguish arrays from objects (an array is an object too)
13+
export function toJSON(value: unknown[]): unknown[];
14+
15+
/**
16+
* JSON encoding does not preserve properties that are undefined
17+
* As a result, deepEqual checks fail because the expected model
18+
* value contains these undefined property values, while the actual
19+
* result returned by REST API does not.
20+
* Use this function to convert a model instance into a data object
21+
* as returned by REST API
22+
*/
23+
export function toJSON(value: object): object;
24+
25+
// The following overloads are provided for convenience.
26+
// In practice, they should not be necessary, as they simply return the input
27+
// value without any modifications.
28+
29+
// tslint:disable-next-line:unified-signatures
30+
export function toJSON(value: undefined): undefined;
31+
export function toJSON(value: null): null;
32+
export function toJSON(value: number): number;
33+
export function toJSON(value: boolean): boolean;
34+
// tslint:disable-next-line:unified-signatures
35+
export function toJSON(value: string): string;
36+
37+
export function toJSON<T>(value: T) {
38+
return JSON.parse(JSON.stringify({value})).value;
39+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright IBM Corp. 2018. All Rights Reserved.
2+
// Node module: @loopback/testlab
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import {expect} from '../../src/expect';
7+
import {toJSON} from '../../src/to-json';
8+
9+
describe('toJSON', () => {
10+
it('removes properties set to undefined', () => {
11+
const value = toJSON({
12+
title: 'a-todo-title',
13+
isComplete: undefined,
14+
});
15+
expectObjectValue(value, {
16+
title: 'a-todo-title',
17+
});
18+
});
19+
20+
it('handles null', () => {
21+
const value = toJSON(null);
22+
expectNull(value);
23+
});
24+
25+
it('handles undefined', () => {
26+
const value = toJSON(undefined);
27+
expectUndefined(value);
28+
});
29+
30+
it('handles numbers', () => {
31+
const value = toJSON(123);
32+
expectNumericValue(value, 123);
33+
});
34+
35+
it('handles strings', () => {
36+
const value = toJSON('text');
37+
expectStringValue(value, 'text');
38+
});
39+
40+
it('handles booleans', () => {
41+
const value = toJSON(true);
42+
expectBooleanValue(value, true);
43+
});
44+
45+
it('handles dates', () => {
46+
const value = toJSON(new Date('2018-01-01T00:00:00.000Z'));
47+
expectStringValue(value, '2018-01-01T00:00:00.000Z');
48+
});
49+
50+
it('handles top-level arrays', () => {
51+
const value = toJSON([1, 'text']);
52+
expectArrayValue(value, [1, 'text']);
53+
});
54+
55+
it('handles nested array', () => {
56+
const value = toJSON({items: ['pen', 'pencil']});
57+
expectObjectValue(value, {items: ['pen', 'pencil']});
58+
});
59+
60+
it('handles functions', () => {
61+
const value = toJSON(function foo() {});
62+
expectUndefined(value);
63+
});
64+
65+
it('handles classes with custom toJSON', () => {
66+
class Customer {
67+
// tslint:disable-next-line:no-unused-variable
68+
private __data: object;
69+
70+
constructor(public id: string, public email: string) {
71+
this.__data = {id, email};
72+
}
73+
74+
toJSON() {
75+
return {id: this.id, email: this.email};
76+
}
77+
}
78+
79+
const value = toJSON(new Customer('an-id', 'test@example.com'));
80+
expectObjectValue(value, {id: 'an-id', email: 'test@example.com'});
81+
expect(value.constructor).to.equal(Object);
82+
});
83+
84+
it('handles nested class instance with custom toJSON', () => {
85+
class Address {
86+
constructor(public street: string, public city: string) {}
87+
88+
toJSON() {
89+
return {
90+
model: 'Address',
91+
street: this.street,
92+
city: this.city,
93+
};
94+
}
95+
}
96+
97+
class Customer {
98+
constructor(public email: string, public address: Address) {}
99+
100+
toJSON() {
101+
return {
102+
model: 'Customer',
103+
email: this.email,
104+
address: this.address,
105+
};
106+
}
107+
}
108+
109+
const value = toJSON(
110+
new Customer(
111+
'test@example.com',
112+
new Address('10 Downing Street', 'London'),
113+
),
114+
);
115+
116+
expectObjectValue(value, {
117+
model: 'Customer',
118+
email: 'test@example.com',
119+
address: {
120+
model: 'Address',
121+
street: '10 Downing Street',
122+
city: 'London',
123+
},
124+
});
125+
126+
expect(value.constructor).to.equal(Object);
127+
expect((value as Customer).address.constructor).to.equal(Object);
128+
});
129+
});
130+
131+
// Helpers to enforce compile-time type checks
132+
133+
function expectStringValue(actual: string, expected: string) {
134+
expect(actual).to.equal(expected);
135+
}
136+
137+
function expectNumericValue(actual: number, expected: number) {
138+
expect(actual).to.equal(expected);
139+
}
140+
141+
function expectBooleanValue(actual: boolean, expected: boolean) {
142+
expect(actual).to.equal(expected);
143+
}
144+
145+
function expectObjectValue(actual: object, expected: object) {
146+
expect(actual).to.deepEqual(expected);
147+
}
148+
149+
function expectArrayValue<T>(actual: T[], expected: T[]) {
150+
expect(actual).to.deepEqual(expected);
151+
}
152+
153+
function expectNull(actual: null) {
154+
expect(actual).to.be.null();
155+
}
156+
157+
function expectUndefined(actual: undefined) {
158+
expect(actual).to.be.undefined();
159+
}

0 commit comments

Comments
 (0)