From 5e16fd5a0043cc1169d64ab1d4733c8434b9e8ba Mon Sep 17 00:00:00 2001 From: arthur Date: Tue, 24 Jul 2018 17:25:03 +0200 Subject: [PATCH 1/3] [ARO] Adding a feature to make tester able to create their own test --- .gitignore | 3 ++- README.md | 23 +++++++++++++++++- src/ExpectedMessage.ts | 53 +++++++++++++++++++++++++++++++++--------- test/mocha.opts | 2 +- 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 2ccc4a1..1e738fa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ yarn-error.log .coveralls.yml .nyc_output coverage -*.ava.spec.js \ No newline at end of file +*.ava.spec.js +.idea \ No newline at end of file diff --git a/README.md b/README.md index 1cefbbf..6284b76 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,28 @@ describe('BotTester', () => { .runTest(); }); ``` - +# Make your own tests +```javascript + it("should make you able to write your own tests", () => { + bot.dialog('/', (session) => { + session.send("Hello"); + session.send("12"); + }); + return botTester.sendMessageToBot('Bonjour',(message) => { + if (message.text === "Hello") { + return true; + } else { + return new Error("Message : " + message.text + "is not equal to 'Hello'") + } + },(message)=>{ + if (parseInt(message.text,0) % 2 === 0) { + return true; + } else { + return new Error("Message is not an even number : " + message.text); + } + }).runTest(); + }); +``` # Address/multiuser cases ```javascript describe('Address/multi user', () => { diff --git a/src/ExpectedMessage.ts b/src/ExpectedMessage.ts index 99e65b4..14b9b92 100644 --- a/src/ExpectedMessage.ts +++ b/src/ExpectedMessage.ts @@ -1,17 +1,19 @@ -import { IEvent, IMessage } from 'botbuilder'; -import { BotTesterExpectation } from './assertionLibraries/BotTesterExpectation'; -import { IConfig } from './config'; +import {IEvent, IMessage} from 'botbuilder'; +import {assert} from 'chai'; +import {BotTesterExpectation} from './assertionLibraries/BotTesterExpectation'; +import {IConfig} from './config'; export enum ExpectedMessageType { String, IMessage, - Regex + Regex, + Function } /** * Types accepted for responses checkers */ -export type PossibleExpectedMessageType = string | IMessage | RegExp | IEvent; +export type PossibleExpectedMessageType = string | IMessage | RegExp | IEvent | Function; /** * Response expectations area always collections. The collection is the set of possible responses, chosen at random. If the collection size @@ -26,8 +28,10 @@ function getExpectedMessageType(expectedResponseCollection: PossibleExpectedMess return ExpectedMessageType.String; } else if (firstElt.constructor.name === 'RegExp') { return ExpectedMessageType.Regex; - } else { + } else if (firstElt.constructor.name === 'IMessage') { return ExpectedMessageType.IMessage; + } else { + return ExpectedMessageType.Function; } } @@ -42,10 +46,8 @@ export class ExpectedMessage { */ private readonly expectedResponseCollection: PossibleExpectedMessageCollections; - constructor( - config: IConfig, - expectedResponseCollection: PossibleExpectedMessageType | PossibleExpectedMessageCollections - ) { + constructor(config: IConfig, + expectedResponseCollection: PossibleExpectedMessageType | PossibleExpectedMessageCollections) { this.internalExpectation = new BotTesterExpectation(config); if (!(expectedResponseCollection instanceof Array)) { @@ -73,6 +75,9 @@ export class ExpectedMessage { // doing this check will highlight if the diff in text instead of a large IMessage diff this.deepMessageMatchCheck(outgoingMessage); break; + case ExpectedMessageType.Function: + this.deepMatchCheckWithFunction(outgoingMessage); + break; default: this.internalExpectation.expect(outgoingMessage.type).toEqual('save'); } @@ -111,7 +116,7 @@ export class ExpectedMessage { const regexCollection: RegExp[] = this.expectedResponseCollection as RegExp[]; this.internalExpectation.expect(regexCollection.some((regex: RegExp) => regex.test(text)), - `'${text}' did not match any regex in ${regexCollection}`).toBeTrue(); + `'${text}' did not match any regex in ${regexCollection}`).toBeTrue(); } /** @@ -136,4 +141,30 @@ export class ExpectedMessage { this.internalExpectation.expect(expectedResponseCollectionAsIMessage).toDeeplyInclude(outgoingMessage); } + + /** + * Verfy the incoming message with custom test defined by tester + * If the function that tester defined return an error, make the test break + * If the function return anything else, the test is considered as good + * I've tryed to use promise as parameter, but in a promise we change scope, so the assert doesn't work + * @param {IMessage} outgoingMessage outgoing message being compared + */ + private deepMatchCheckWithFunction(outgoingMessage: IMessage): void { + const functionCollection: Function[] = this.expectedResponseCollection as Function[]; + let errorString = ''; + let success = false; + functionCollection.forEach((func: Function) => { + const result = func(outgoingMessage); + if (result instanceof Error) { + errorString += `\n -----------------ERROR-----------------\n\n\n'${result.message}' `; + } else { + success = true; + } + }); + // ErrorString here, can hold multiples error, if the bot send multiples message in one batching + const error = `Bot should have relied response that matches with function but respond '${outgoingMessage}'` + + ` that create the following error(s) '${errorString}'`; + this.internalExpectation.expect(success, error).toBeTrue(); + + } } diff --git a/test/mocha.opts b/test/mocha.opts index 34e73da..af4f4df 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,3 +1,3 @@ --require ts-node/register --recursive -test/*.spec.ts \ No newline at end of file +test/**/*mocha.spec.ts \ No newline at end of file From f2c97dc194e4a11a67499692ef63d3d7f88c5241 Mon Sep 17 00:00:00 2001 From: arthur Date: Tue, 24 Jul 2018 18:10:42 +0200 Subject: [PATCH 2/3] [ARO] Reset of the if who chose the type of test --- src/ExpectedMessage.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ExpectedMessage.ts b/src/ExpectedMessage.ts index 14b9b92..20b2377 100644 --- a/src/ExpectedMessage.ts +++ b/src/ExpectedMessage.ts @@ -28,10 +28,10 @@ function getExpectedMessageType(expectedResponseCollection: PossibleExpectedMess return ExpectedMessageType.String; } else if (firstElt.constructor.name === 'RegExp') { return ExpectedMessageType.Regex; - } else if (firstElt.constructor.name === 'IMessage') { - return ExpectedMessageType.IMessage; - } else { + } else if (firstElt.constructor.name === 'Function') { return ExpectedMessageType.Function; + } else { + return ExpectedMessageType.IMessage; } } From 1ee88fe373b768afa7ff94f92a828ed38063156c Mon Sep 17 00:00:00 2001 From: arthur Date: Wed, 25 Jul 2018 11:36:59 +0200 Subject: [PATCH 3/3] [ARO] Add unit test + interpretation of string return --- src/ExpectedMessage.ts | 14 +-- test/mocha/chai/BotTester.mocha.spec.ts | 89 ++++++++++++++----- .../mocha/chai/BotTesterFailure.mocha.spec.ts | 35 +++++++- 3 files changed, 108 insertions(+), 30 deletions(-) diff --git a/src/ExpectedMessage.ts b/src/ExpectedMessage.ts index 20b2377..1fb61b2 100644 --- a/src/ExpectedMessage.ts +++ b/src/ExpectedMessage.ts @@ -1,5 +1,4 @@ import {IEvent, IMessage} from 'botbuilder'; -import {assert} from 'chai'; import {BotTesterExpectation} from './assertionLibraries/BotTesterExpectation'; import {IConfig} from './config'; @@ -116,7 +115,7 @@ export class ExpectedMessage { const regexCollection: RegExp[] = this.expectedResponseCollection as RegExp[]; this.internalExpectation.expect(regexCollection.some((regex: RegExp) => regex.test(text)), - `'${text}' did not match any regex in ${regexCollection}`).toBeTrue(); + `'${text}' did not match any regex in ${regexCollection}`).toBeTrue(); } /** @@ -146,17 +145,19 @@ export class ExpectedMessage { * Verfy the incoming message with custom test defined by tester * If the function that tester defined return an error, make the test break * If the function return anything else, the test is considered as good - * I've tryed to use promise as parameter, but in a promise we change scope, so the assert doesn't work * @param {IMessage} outgoingMessage outgoing message being compared */ private deepMatchCheckWithFunction(outgoingMessage: IMessage): void { const functionCollection: Function[] = this.expectedResponseCollection as Function[]; let errorString = ''; + const exceptedResponsesStrings = []; let success = false; functionCollection.forEach((func: Function) => { const result = func(outgoingMessage); if (result instanceof Error) { errorString += `\n -----------------ERROR-----------------\n\n\n'${result.message}' `; + } else if (typeof result === 'string') { + exceptedResponsesStrings.push(result); } else { success = true; } @@ -164,7 +165,10 @@ export class ExpectedMessage { // ErrorString here, can hold multiples error, if the bot send multiples message in one batching const error = `Bot should have relied response that matches with function but respond '${outgoingMessage}'` + ` that create the following error(s) '${errorString}'`; - this.internalExpectation.expect(success, error).toBeTrue(); - + if (exceptedResponsesStrings.length > 0) { + this.checkMessageTextForExactStringMatch(outgoingMessage, exceptedResponsesStrings); + } else { + this.internalExpectation.expect(success, error).toBeTrue(); + } } } diff --git a/test/mocha/chai/BotTester.mocha.spec.ts b/test/mocha/chai/BotTester.mocha.spec.ts index 2f90102..35a0a71 100644 --- a/test/mocha/chai/BotTester.mocha.spec.ts +++ b/test/mocha/chai/BotTester.mocha.spec.ts @@ -1,10 +1,10 @@ //```javascript -import { IAddress, IMessage, Message, Prompts, Session, UniversalBot } from 'botbuilder'; +import {IAddress, IMessage, Message, Prompts, Session, UniversalBot} from 'botbuilder'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { BotTester } from './../../../src/BotTester'; -import { TestConnector } from './../../../src/TestConnector'; -import { getAdaptiveCard, getAdaptiveCardAttachment, getAdaptiveCardMessage } from './../../adaptiveCardProvider'; +import {BotTester} from './../../../src/BotTester'; +import {TestConnector} from './../../../src/TestConnector'; +import {getAdaptiveCard, getAdaptiveCardAttachment, getAdaptiveCardMessage} from './../../adaptiveCardProvider'; chai.use(chaiAsPromised); const expect = chai.expect; @@ -105,12 +105,12 @@ describe('BotTester', () => { bot.dialog('/', [(session) => { new Prompts.text(session, 'What would you like to set data to?'); }, (session, results) => { - session.userData = { data: results.response }; + session.userData = {data: results.response}; session.save(); }]); return new BotTester(bot) - .sendMessageToBot('Start this thing!', 'What would you like to set data to?') + .sendMessageToBot('Start this thing!', 'What would you like to set data to?') .sendMessageToBotAndExpectSaveWithNoResponse('This is data!') .checkSession((session) => { expect(session.userData).not.to.be.null; @@ -152,16 +152,18 @@ describe('BotTester', () => { //# Address/multiuser cases //```javascript describe('Address/multi user', () => { - const defaultAddress = { channelId: 'console', - user: { id: 'customUser1', name: 'A' }, - bot: { id: 'customBot1', name: 'Bot1' }, - conversation: { id: 'customUser1Conversation' } + const defaultAddress = { + channelId: 'console', + user: {id: 'customUser1', name: 'A'}, + bot: {id: 'customBot1', name: 'Bot1'}, + conversation: {id: 'customUser1Conversation'} }; - const user2Address = { channelId: 'console', - user: { id: 'user2', name: 'B' }, - bot: { id: 'bot', name: 'Bot' }, - conversation: { id: 'user2Conversation' } + const user2Address = { + channelId: 'console', + user: {id: 'user2', name: 'B'}, + bot: {id: 'bot', name: 'Bot'}, + conversation: {id: 'user2Conversation'} }; beforeEach(() => { @@ -201,7 +203,7 @@ describe('BotTester', () => { //## Can have a default address assigned to the bot //```javascript - // the bot can have a default address that messages are sent to. If needed, the default address can be ignored by sending an IMessage +// the bot can have a default address that messages are sent to. If needed, the default address can be ignored by sending an IMessage it('Can have a default address assigned to it and communicate to multiple users', () => { const askForUser1Name = new Message() .text('What is my name?') @@ -224,8 +226,8 @@ describe('BotTester', () => { .toMessage(); // when testing for an address that is not the default for the bot, the address must be passed in - return new BotTester(bot, { defaultAddress }) - // because user 1 is the default address, the expected responses can be a string + return new BotTester(bot, {defaultAddress}) + // because user 1 is the default address, the expected responses can be a string .sendMessageToBot(askForUser1Name, 'A') .sendMessageToBot('What is my name?', user1ExpectedResponse) .sendMessageToBot(askForUser1Name, user1ExpectedResponse) @@ -238,10 +240,11 @@ describe('BotTester', () => { //# Can test batch responses //```javascript it('can handle batch responses', () => { - const CUSTOMER_ADDRESS: IAddress = { channelId: 'console', - user: { id: 'userId1', name: 'user1' }, - bot: { id: 'bot', name: 'Bot' }, - conversation: { id: 'user1Conversation' } + const CUSTOMER_ADDRESS: IAddress = { + channelId: 'console', + user: {id: 'userId1', name: 'user1'}, + bot: {id: 'bot', name: 'Bot'}, + conversation: {id: 'user1Conversation'} }; const msg1 = new Message() @@ -258,7 +261,7 @@ describe('BotTester', () => { bot.send([msg1, msg2]); }); - return new BotTester(bot, { defaultAddress: CUSTOMER_ADDRESS }) + return new BotTester(bot, {defaultAddress: CUSTOMER_ADDRESS}) .sendMessageToBot('anything', 'hello', 'there') .runTest(); }); @@ -282,6 +285,44 @@ describe('BotTester', () => { }); //``` +//# Can test using Function +//```javascript + it('accepts Function', () => { + bot.dialog('/', (session: Session) => { + session.send('hello!'); + session.send('12'); + }); + + const botTester = new BotTester(bot) + .sendMessageToBot('Hi', (message: IMessage) => { + if (message.text === 'hello!') { + return true; + } + }, (message: IMessage) => { + if (parseInt(message.text, 0) % 2 === 0) { + return true; + } + }); + + return botTester.runTest(); + }); +//``` + +//```javascript + it('accepts Function that return string', () => { + bot.dialog('/', (session: Session) => { + session.send('hello!'); + }); + + const botTester = new BotTester(bot) + .sendMessageToBot('Hi', (message: IMessage) => { + return message.text; + }); + + return botTester.runTest(); + }); +//``` + //# variable # args can have mixed type //```javascript it('rest params can have mixed type', () => { @@ -367,7 +408,7 @@ describe('BotTester', () => { const ignoreHowMessage = (message) => !message.text.includes('how'); const ignoreAreMessage = (message) => !message.text.includes('are'); - return new BotTester(bot, { messageFilters: [ignoreHowMessage, ignoreAreMessage]}) + return new BotTester(bot, {messageFilters: [ignoreHowMessage, ignoreAreMessage]}) .sendMessageToBot('intro', 'hello', 'you?') .runTest(); }); @@ -393,7 +434,7 @@ describe('BotTester', () => { it('change timeout time', (done) => { const timeout = 750; bot.dialog('/', (session) => { - setTimeout(() => session.send('hi there'), timeout * 2 ); + setTimeout(() => session.send('hi there'), timeout * 2); }); expect(new BotTester(bot) diff --git a/test/mocha/chai/BotTesterFailure.mocha.spec.ts b/test/mocha/chai/BotTesterFailure.mocha.spec.ts index 0a8ffee..be43c42 100644 --- a/test/mocha/chai/BotTesterFailure.mocha.spec.ts +++ b/test/mocha/chai/BotTesterFailure.mocha.spec.ts @@ -42,7 +42,7 @@ describe('BotTester', () => { }); // ignore this for now. It's more of a debate as to whether or not the user should know not to do this - xit('it will fail if an empty collection is given', () => { + it('it will fail if an empty collection is given', () => { bot.dialog('/', (session: Session) => { session.send('hello!'); }); @@ -193,6 +193,39 @@ describe('BotTester', () => { ).to.eventually.be.rejectedWith('\'abcd\' did not match any regex in /^\\d+/').notify(done); }); + it('will fail if function return an error', (done: Function) => { + bot.dialog('/', (session: Session) => { + session.send('hello!'); + session.send('13'); + }); + + const botTester = new BotTester(bot) + .sendMessageToBot('Hi', (message: IMessage) => { + if (message.text === 'hello!') { + return true; + } + }, (message: IMessage) => { + if (parseInt(message.text, 0) % 2 !== 0) { + return new Error(`Message is not an even number : '${message.text}'`); + } + }); + + expect(botTester.runTest()).to.be.rejected.notify(done); + }); + + it('will fail if function return a bad string', (done: Function) => { + bot.dialog('/', (session: Session) => { + session.send('hello!'); + }); + + const botTester = new BotTester(bot) + .sendMessageToBot('Hi', (message: IMessage) => { + return 'foo'; + }); + + expect(botTester.runTest()).to.be.rejected.notify(done); + }); + it('can timeout', (done: Function) => { const timeout = 1000; bot.dialog('/', (session: Session) => {