Skip to content
This repository has been archived by the owner on Feb 20, 2024. It is now read-only.

Commit

Permalink
feat(#10) non-deterministic mocks
Browse files Browse the repository at this point in the history
  • Loading branch information
mscottx88 committed Dec 8, 2018
1 parent f242048 commit c21b3a7
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 9 deletions.
29 changes: 25 additions & 4 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,40 @@ export default class Context {
* B. Saved to disk if in record mode
*/
public interceptedRequestsCompleted: ISerializedHttp[] = [];
/**
* Serialized records loaded from disk.
*/
public loadedMocks: ISerializedHttp[] = [];
/**
* Proxied requests which have not yet responded. When completed
* the value is set to "null" but the index is preserved.
*/
public inFlightRequests: Array<IInFlightRequest | null> = [];

/**
* Serialized records loaded from disk.
*/
private mocks: ISerializedHttp[] = [];
/**
* Mocks which have already been consumed. The ordinal position of
* each entry is a boolean that, when true, indicates the parallel
* array entry in loadedMocks has been consumed.
*/
private consumed: boolean[] = [];

public clear() {
this.interceptedRequestsCompleted = [];
this.inFlightRequests = [];
this.loadedMocks = [];
}

public get consumedMocks(): boolean[] {
return this.consumed;
}

public get loadedMocks(): ISerializedHttp[] {
return this.mocks;
}
public set loadedMocks(value: ISerializedHttp[]) {
this.mocks = value;

// reset consumed mocks
this.consumed = [];
}
}
60 changes: 55 additions & 5 deletions src/yesno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,60 @@ export class YesNo implements IFiltered {
});
}

/**
* Try to consume the mock.
* @param serializedRequest
* @param mock
* @param requestIndex
*/
private tryConsumeMock(
serializedRequest: ISerializedRequest,
mock: ISerializedHttp,
requestIndex: number,
) {
if (this.ctx.consumedMocks[requestIndex]) {
return false;
}

try {
// compare the request and the mock
comparator.byUrl(serializedRequest, mock.request, { requestIndex });

// if the comparator does not throw, then the mock is matching- mark it as consumed
this.ctx.consumedMocks[requestIndex] = true;
return true;
} catch (error) {
// if there is only one loaded mock, and it doesn't match, throw the detailed error
if (this.ctx.loadedMocks.length === 1) {
throw error;
}

return false;
}
}

/**
* Given a serialized request and request number, find the appropriately-matching mock.
* @param serializedRequest
* @param requestNumber
*/
private findMatchingMock(
serializedRequest: ISerializedRequest,
requestNumber: number,
): ISerializedHttp | undefined {
// first see if the mock in the ordinal position can be consumed
const ordinalMock: ISerializedHttp = this.ctx.loadedMocks[requestNumber];
if (this.tryConsumeMock(serializedRequest, ordinalMock, requestNumber)) {
return ordinalMock;
}

// otherwise, try to consume the first matching mock
return this.ctx.loadedMocks.find(
(mock: ISerializedHttp, index: number): boolean =>
this.tryConsumeMock(serializedRequest, mock, index),
);
}

private async mockResponse({
clientRequest,
interceptedRequest,
Expand All @@ -294,16 +348,12 @@ export class YesNo implements IFiltered {
await (readable as any).pipeline(interceptedRequest, requestSerializer);

const serializedRequest = requestSerializer.serialize();
const mock = this.ctx.loadedMocks[requestNumber];
const mock = this.findMatchingMock(serializedRequest, requestNumber);

if (!mock) {
throw new YesNoError(`No mock found for request #${requestNumber}`);
}

// Assertion must happen before promise -
// mitm does not support promise rejections on "request" event
comparator.byUrl(serializedRequest, mock.request, { requestIndex: requestNumber });

const bodyString = _.isPlainObject(mock.response.body)
? JSON.stringify(mock.response.body)
: mock.response.body;
Expand Down
25 changes: 25 additions & 0 deletions test/unit/yesno.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,31 @@ describe('Yesno', () => {
expect(yesno.intercepted()).to.have.lengthOf(1);
});

it('should scan for and use a mock just once', async () => {
yesno.mock([
createMock(),
createMock({
request: {
port: 4000,
},
}),
createMock(),
]);

expect(yesno.intercepted()).to.have.lengthOf(0);

// consumes first mock
await requestTestServer();

// consumes third mock
await requestTestServer();

expect(yesno.intercepted()).to.have.lengthOf(2);

// fails because the remaining mock is not matching
await expect(mockedRequest()).to.be.rejectedWith(/YesNo: No mock found for request #2/);
});

it('should reject a request for which no mock has been provided');
it('should handle unexpected errors');

Expand Down

0 comments on commit c21b3a7

Please sign in to comment.