Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ yarn-error.log
.coveralls.yml
.nyc_output
coverage
*.ava.spec.js
*.ava.spec.js
.idea
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
53 changes: 44 additions & 9 deletions src/ExpectedMessage.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { IEvent, IMessage } from 'botbuilder';
import { BotTesterExpectation } from './assertionLibraries/BotTesterExpectation';
import { IConfig } from './config';
import {IEvent, IMessage} from 'botbuilder';
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
Expand All @@ -26,6 +27,8 @@ function getExpectedMessageType(expectedResponseCollection: PossibleExpectedMess
return ExpectedMessageType.String;
} else if (firstElt.constructor.name === 'RegExp') {
return ExpectedMessageType.Regex;
} else if (firstElt.constructor.name === 'Function') {
return ExpectedMessageType.Function;
} else {
return ExpectedMessageType.IMessage;
}
Expand All @@ -42,10 +45,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)) {
Expand Down Expand Up @@ -73,6 +74,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');
}
Expand Down Expand Up @@ -136,4 +140,35 @@ 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
* @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;
}
});
// 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}'`;
if (exceptedResponsesStrings.length > 0) {
this.checkMessageTextForExactStringMatch(outgoingMessage, exceptedResponsesStrings);
} else {
this.internalExpectation.expect(success, error).toBeTrue();
}
}
}
2 changes: 1 addition & 1 deletion test/mocha.opts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
--require ts-node/register
--recursive
test/*.spec.ts
test/**/*mocha.spec.ts
89 changes: 65 additions & 24 deletions test/mocha/chai/BotTester.mocha.spec.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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?')
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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();
});
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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();
});
Expand All @@ -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)
Expand Down
35 changes: 34 additions & 1 deletion test/mocha/chai/BotTesterFailure.mocha.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!');
});
Expand Down Expand Up @@ -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) => {
Expand Down