Skip to content

Repository files navigation

Bot Tester for Bot Builder Framework CircleCInpm versionCoverage Status

Simple framework that allows for easy testing of a botbuiler chatbot using mocha and chai.

install

npm install --save bot-tester

supported test libraries

  1. mocha/chai
  2. ava

Class definitions, see the BotTester Framework reference docs

config

config can be set one of 2 ways:

  1. creating a bot-tester.json file in the root directory of your project.
  2. passing in a config object into the options param when instantiating new instances of the BotTester

Passing in the config overrides any default values or values set by bot-tester.json. At the moment, the options are:

{defaultAddress: botbuilder.IAddress,timeout: number// in milliseconds// each filter returns false for messages that the BotTester should ignoremessageFilters: ((message:IMessage)=>boolean)[],ignoreTypingEvent: boolean,ignoreEndOfConversationEvent: boolean,// Setting this to true will cause checkSession to hang and test to fail.ignoreInternalSaveMessage: boolean,// this will allow using different assertion libraries. Currently supported libraries are:// 1) chai// 2) ava// should this field not be provided, it will default to chaiassertionLibrary: string,// certain test runners need to pass in a context. Currently, the following runners require this// 1) avatestContext: {}}

if timeout is defined, then a particular runTest() call will fail if it does not receive each expected message within the timeout period of time set in the options.

additionally, config can be set by using the config updating options in the build chain, documented below in the example use

For a more in depth view, check out the Bot Tester Framework Config doc

Test runner setup

Mocha/Chai

const{ UniversalBot }=require('botbuilder');const{ expect }=require('chai');const{ BotTester, TestConnector }=require('bot-tester');describe('BotTester',()=>{it('can handle a single response',()=>{constbot=newUniversalBot(newTestConnector());bot.dialog('/',(session)=>{session.send('hello!');});constbotTesterOptions={// the assertionLibrary field can be set in the bot-tester.json, but the default is mocha/chai. It can be omitted if the desired assertion library to use is chai.assertionLibrary: 'chai',// testContext is not used for chai expectations}returnnewBotTester(bot,botTesterOptions).sendMessageToBot('Hola!','hello!').runTest();});

Ava

consttest=require('ava');const{ UniversalBot }=require('botbuilder');const{ BotTester, TestConnector }=require('bot-tester');test('can handle a single response',(t)=>{constbot=newUniversalBot(newTestConnector());bot.dialog('/',(session)=>{session.send('hello!');});constbotTesterOptions={// the assertionLibrary field can be set in the bot-tester.json, but testContext must be passed in for ava testsassertionLibrary: 'ava',testContext: t}returnnewBotTester(bot,botTesterOptions).sendMessageToBot('Hola!','hello!').runTest();});

Example Usage

All examples use mocha/chai, but the content will be similar regardless of the test runner

import{IAddress,IMessage,Message,Prompts,Session,UniversalBot}from'botbuilder';import{expect}from'chai';import{BotTester,TestConnector}from'bot-tester';import{getAdaptiveCard,getAdaptiveCardAttachment,getAdaptiveCardMessage}from'./adaptiveCardProvider';constconnector=newTestConnector();describe('BotTester',()=>{letbot;beforeEach(()=>{bot=newUniversalBot(connector);});// ... tests live here!

Test for single response

it('can handle a single response',()=>{bot.dialog('/',(session)=>{session.send('hello!');});constbotTester=newBotTester(bot).sendMessageToBot('Hola!','hello!');returnbotTester.runTest();});

Test for multiple responses

it('can handle multiple responses',()=>{bot.dialog('/',(session)=>{session.send('hello!');session.send('how are you doing?');});newBotTester(bot).sendMessageToBot('Hola!','hello!','how are you doing?').runTest();});

Test for random response arrays

// re-run the test multiple times to guarantee that multiple colors are returnedletrandomResponseRunCounter=5;constrandomColors=['red','green','blue','grey','gray','purple','magenta','cheese','orange','hazelnut'];while(randomResponseRunCounter--){it('Can handle random responses',()=>{bot.dialog('/',(session)=>{session.send(randomColors);});returnnewBotTester(bot).sendMessageToBot('tell me a color!',randomColors).runTest();});}

Test with prompts

it('can test prompts',()=>{bot.dialog('/',[(session)=>{newPrompts.text(session,'Hi there! Tell me something you like');},(session,results)=>{session.send(`${results.response} is pretty cool.`);newPrompts.text(session,'Why do you like it?');},(session)=>session.send('Interesting. Well, that\'s all I have for now')]);returnnewBotTester(bot).sendMessageToBot('Hola!','Hi there! Tell me something you like').sendMessageToBot('The sky','The sky is pretty cool.','Why do you like it?').sendMessageToBot('It\'s blue','Interesting. Well, that\'s all I have for now').runTest();});

Test Adaptive Cards

it('can correctly check against adaptive cards',()=>{bot.dialog('/',(session)=>{session.send(getAdaptiveCardMessage());});returnnewBotTester(bot).sendMessageToBot('anything',getAdaptiveCardMessage()).runTest();});

Inspect session

it('can inspect session state',()=>{bot.dialog('/',[(session)=>{newPrompts.text(session,'What would you like to set data to?');},(session,results)=>{session.userData={data: results.response};session.save();}]);returnnewBotTester(bot).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;expect(session.userData.data).to.be.equal('This is data!');}).runTest();});

Test custom messages

it('can handle custom messages in response',()=>{constcustomMessage: {someField?: {}}&IMessage=newMessage().text('this is text').toMessage();customMessage.someField={a: 1};customMessage.type='newType';constmatchingCustomMessage: {someField?: {}}&IMessage=newMessage().toMessage();matchingCustomMessage.text='this is text';matchingCustomMessage.type='newType';bot.dialog('/',(session: Session)=>{session.send(customMessage);});returnnewBotTester(bot).sendMessageToBot('anything',customMessage).sendMessageToBot('anything',matchingCustomMessage).runTest();});

Address/multiuser cases

describe('Address/multi user',()=>{constdefaultAddress={channelId: 'console',user: {id: 'user1',name: 'A'},bot: {id: 'bot',name: 'Bot'},conversation: {id: 'user1Conversation'}};constuser2Address={channelId: 'console',user: {id: 'user2',name: 'B'},bot: {id: 'bot',name: 'Bot'},conversation: {id: 'user2Conversation'}};beforeEach(()=>{bot.dialog('/',(session)=>session.send(session.message.address.user.name));});

Can check addressess, including partial addresses

it('can ensure proper address being used for routing. Includes partial address',()=>{constaskForUser1Name=newMessage().text('What is my name?').address(defaultAddress).toMessage();constexpectedAddressInMessage=newMessage().address(defaultAddress).toMessage();constaddr={user: {id: 'user1'}}asIAddress;// partial addresses work as well (i.e. if you only want to check one field such as userId)constexpectedPartialAddress=newMessage().address(addr).toMessage();returnnewBotTester(bot).sendMessageToBot(askForUser1Name,expectedAddressInMessage).sendMessageToBot(askForUser1Name,expectedPartialAddress).runTest();});

Can have a default address assigned to the bot

// the bot can have a default address that messages are sent to. If needed, the default address can be ignored by sending an IMessageit('Can have a default address assigned to it and communicate to multiple users',()=>{constaskForUser1Name=newMessage().text('What is my name?').address(defaultAddress).toMessage();constaskForUser2Name=newMessage().text('What is my name?').address(user2Address).toMessage();constuser1ExpectedResponse=newMessage().text('A').address(defaultAddress).toMessage();constuser2ExpectedResponse=newMessage().text('B').address(user2Address).toMessage();// when testing for an address that is not the default for the bot, the address must be passed inreturnnewBotTester(bot,{ defaultAddress })// because user 1 is the default address, the expected responses can be a string.sendMessageToBot(askForUser1Name,'A').sendMessageToBot(askForUser1Name,user1ExpectedResponse).sendMessageToBot(askForUser2Name,user2ExpectedResponse).runTest();});});

Can test batch responses

it('can handle batch responses',()=>{constCUSTOMER_ADDRESS: IAddress={channelId: 'console',user: {id: 'userId1',name: 'user1'},bot: {id: 'bot',name: 'Bot'},conversation: {id: 'user1Conversation'}};constmsg1=newMessage().address(CUSTOMER_ADDRESS).text('hello').toMessage();constmsg2=newMessage().address(CUSTOMER_ADDRESS).text('there').toMessage();bot.dialog('/',(session: Session)=>{bot.send([msg1,msg2]);});returnnewBotTester(bot,{defaultAddress: CUSTOMER_ADDRESS}).sendMessageToBot('anything','hello','there').runTest();});

Can test using regex

it('accepts RegExp',()=>{constnumberRegex=/^\d+/;bot.dialog('/',(session)=>{// send only numbers for this test case ....session.send(session.message.text);});returnnewBotTester(bot).sendMessageToBot('1',numberRegex).sendMessageToBot('3156',numberRegex).sendMessageToBot('8675309',numberRegex).runTest();});

variable # args can have mixed type

it('rest params can have mixed type',()=>{constnumberRegex=/^\d+/;bot.dialog('/',(session)=>{session.send(session.message.text);session.send(session.message.text);});returnnewBotTester(bot).sendMessageToBot('123',numberRegex,'123').runTest();});

Can perform arbitrary work between test steps

it('can do arbitrary work between test steps',()=>{letresponseString='goodbye';bot.dialog('/',(session)=>{// send only numbers for this test case ....session.send(responseString);});returnnewBotTester(bot).sendMessageToBot('you say','goodbye').then(()=>responseString='hello').sendMessageToBot('and i say','hello').runTest();});

Can wait between test steps

it('can wait between test steps',()=>{constdelay=1000;letbeforeDelayTime;letafterDelayTime;bot.dialog('/',(session)=>{// send only numbers for this test case ....if(afterDelayTime-beforeDelayTime>=delay){session.send('i waited some time');}});returnnewBotTester(bot).then(()=>beforeDelayTime=Date.now()).wait(delay).then(()=>afterDelayTime=Date.now()).sendMessageToBot('have you waited ?','i waited some time').runTest();});

can check messages while ignoring order

it('can accept messages without expectations for order',()=>{bot.dialog('/',(session)=>{session.send('hi');session.send('there');session.send('how are you?');});returnnewBotTester(bot).sendMessageToBotIgnoringResponseOrder('anything','how are you?','hi','there').runTest();});

can apply one or more message filters in the BotTester options for messages to ignore

it('can apply one or more message filters in the BotTester options for messages to ignore',()=>{bot.dialog('/',(session)=>{session.send('hello');session.send('how');session.send('are');session.send('you?');});constignoreHowMessage=(message)=>!message.text.includes('how');constignoreAreMessage=(message)=>!message.text.includes('are');returnnewBotTester(bot,{messageFilters: [ignoreHowMessage,ignoreAreMessage]}).sendMessageToBot('intro','hello','you?').runTest();});

can modify BotTester options

describe('can modifiy options in line in builder chain',()=>{it('add a message filter',()=>{bot.dialog('/',(session)=>{session.send('hello');session.send('there');session.send('green');});returnnewBotTester(bot).addMessageFilter((msg)=>!msg.text.includes('hello')).addMessageFilter((msg)=>!msg.text.includes('there')).sendMessageToBot('hey','green').runTest();});it('change timeout time',(done)=>{consttimeout=750;bot.dialog('/',(session)=>{setTimeout(()=>session.send('hi there'),timeout*2);});expect(newBotTester(bot).setTimeout(timeout).sendMessageToBot('hey','hi there').runTest()).to.be.rejected.notify(done);});it('can ignore typing events',()=>{bot.dialog('/',(session)=>{session.send('hello');session.sendTyping();session.send('goodbye');});returnnewBotTester(bot).ignoreTypingEvent().sendMessageToBot('hey','hello','goodbye').runTest();});});

About

A simple bot testing framework for the bot builder framework. Easily send messages to a bot and check for expected responses and inspect session inside a dialog

Resources

Stars

55 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages