{"action":"created","issue":{"url":"https://api.github.com/repos/Microsoft/botbuilder-js/issues/84","repository_url":"https://api.github.com/repos/Microsoft/botbuilder-js","labels_url":"https://api.github.com/repos/Microsoft/botbuilder-js/issues/84/labels{/name}","comments_url":"https://api.github.com/repos/Microsoft/botbuilder-js/issues/84/comments","events_url":"https://api.github.com/repos/Microsoft/botbuilder-js/issues/84/events","html_url":"https://github.com/Microsoft/botbuilder-js/issues/84","id":297973012,"number":84,"title":"Need a TestBotContext","user":{"login":"GeekTrainer","id":6109729,"avatar_url":"https://avatars1.githubusercontent.com/u/6109729?v=4","gravatar_id":"","url":"https://api.github.com/users/GeekTrainer","html_url":"https://github.com/GeekTrainer","followers_url":"https://api.github.com/users/GeekTrainer/followers","following_url":"https://api.github.com/users/GeekTrainer/following{/other_user}","gists_url":"https://api.github.com/users/GeekTrainer/gists{/gist_id}","starred_url":"https://api.github.com/users/GeekTrainer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GeekTrainer/subscriptions","organizations_url":"https://api.github.com/users/GeekTrainer/orgs","repos_url":"https://api.github.com/users/GeekTrainer/repos","events_url":"https://api.github.com/users/GeekTrainer/events{/privacy}","received_events_url":"https://api.github.com/users/GeekTrainer/received_events","type":"User","site_admin":false},"labels":[{"id":801045981,"url":"https://api.github.com/repos/Microsoft/botbuilder-js/labels/enhancement","name":"enhancement","color":"84b6eb","default":true}],"state":"closed","locked":false,"assignee":{"login":"Stevenic","id":698939,"avatar_url":"https://avatars2.githubusercontent.com/u/698939?v=4","gravatar_id":"","url":"https://api.github.com/users/Stevenic","html_url":"https://github.com/Stevenic","followers_url":"https://api.github.com/users/Stevenic/followers","following_url":"https://api.github.com/users/Stevenic/following{/other_user}","gists_url":"https://api.github.com/users/Stevenic/gists{/gist_id}","starred_url":"https://api.github.com/users/Stevenic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Stevenic/subscriptions","organizations_url":"https://api.github.com/users/Stevenic/orgs","repos_url":"https://api.github.com/users/Stevenic/repos","events_url":"https://api.github.com/users/Stevenic/events{/privacy}","received_events_url":"https://api.github.com/users/Stevenic/received_events","type":"User","site_admin":false},"assignees":[{"login":"Stevenic","id":698939,"avatar_url":"https://avatars2.githubusercontent.com/u/698939?v=4","gravatar_id":"","url":"https://api.github.com/users/Stevenic","html_url":"https://github.com/Stevenic","followers_url":"https://api.github.com/users/Stevenic/followers","following_url":"https://api.github.com/users/Stevenic/following{/other_user}","gists_url":"https://api.github.com/users/Stevenic/gists{/gist_id}","starred_url":"https://api.github.com/users/Stevenic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Stevenic/subscriptions","organizations_url":"https://api.github.com/users/Stevenic/orgs","repos_url":"https://api.github.com/users/Stevenic/repos","events_url":"https://api.github.com/users/Stevenic/events{/privacy}","received_events_url":"https://api.github.com/users/Stevenic/received_events","type":"User","site_admin":false}],"milestone":null,"comments":1,"created_at":"2018-02-17T02:58:10Z","updated_at":"2018-04-17T02:05:20Z","closed_at":"2018-04-17T02:05:20Z","author_association":"MEMBER","body":"After spending a week creating middleware, and doing it with TDD, the thing I settled on above all is the need for a TestBotContext. When calling any of the middleware functions (events), what's really happening is the state of the context is being modified in many (most?) cases.\r\n\r\nFor example, in the case of human handoff, where messages need to be sent back on the context passed in, and not via the bot, all that's really happening is the responses array is having another `Activity` pushed on it. Something like this (with logic assumed):\r\n\r\n``` javascript\r\nonReceive(context: Partial<BotContext>, next: () => Promise<void>) {\r\n  if(userWantsAgent) context.reply(\"Connecting you to an agent.\"); // modifies context.responses\r\n}\r\n```\r\n\r\nIt's much simpler to test if I can pass a context in, and then inspect it afterwards, to ensure there's a particular reply waiting in the wings. As best I can tell, there isn't an easy way to just create an empty context, so I wound up implementing one on my own. The moment I did that, my tests became easier to write and more elegant. My implementation isn't complete, but it was what I needed for my tests.\r\n\r\n``` typescript\r\nclass TestContext implements BotContext {\r\n    constructor(conversationReference: ConversationReference, request: Activity | string) {\r\n        if(typeof request !== \"object\") {\r\n            request = { type: \"message\", text: request };\r\n        }\r\n        this.conversationReference = conversationReference;\r\n        this.request = request;\r\n        this.bot = new Bot(new TestAdapter);\r\n        this.bot.createContext =\r\n            (reference: ConversationReference, onReady: (context: BotContext) => void) => {\r\n                onReady(this);\r\n                return new Promise(() => {});\r\n            };\r\n\r\n        this.responses = [];\r\n    }\r\n    public request: Activity;\r\n    public responses: Activity[];\r\n    public bot: Bot;\r\n    public responded: boolean = false;\r\n    public conversationReference: ConversationReference;\r\n    public state: BotState;\r\n    public templateManager: TemplateManager;\r\n\r\n    public delay(duration: number) { return this; }\r\n    public dispose() {}\r\n    public endOfConversation(code?: string) { return this; }\r\n    public replyWith(id: string, data: any) { return this; }\r\n    public flushResponses() { \r\n        return new Promise<ConversationResourceResponse[]>(\r\n            (value) => {}\r\n        );\r\n    }\r\n    public showTyping() {\r\n        return this;\r\n    }\r\n\r\n    public reply(textOrActivity : string | Activity) {\r\n        if(typeof textOrActivity !== \"object\") {\r\n            textOrActivity = { type: \"message\", text: textOrActivity };\r\n        }\r\n        this.responses.unshift(textOrActivity);\r\n        return this;\r\n    }\r\n}\r\n\r\n// sample test:\r\n    it(\"Agent can list queue\", (done) => {\r\n        const context = new TestContext(agentReference, \"#list\");\r\n        const provider = getProvider(HandoffUserState.queued);\r\n\r\n        new HandoffMiddleware(provider).receiveActivity(context, next);\r\n\r\n        assert(provider.getQueue.called, \"getQueue not called\");\r\n        assert(next.notCalled, \"next called\"); // (a sinon spy)\r\n        assert(context.responses.length === 1, \"Wrong number of responses\");\r\n        assert(context.responses[0].text.indexOf(userReference.user.name) > -1, \"Name not listed\")\r\n        done();\r\n    });\r\n```"},"comment":{"url":"https://api.github.com/repos/Microsoft/botbuilder-js/issues/comments/381806224","html_url":"https://github.com/Microsoft/botbuilder-js/issues/84#issuecomment-381806224","issue_url":"https://api.github.com/repos/Microsoft/botbuilder-js/issues/84","id":381806224,"user":{"login":"Stevenic","id":698939,"avatar_url":"https://avatars2.githubusercontent.com/u/698939?v=4","gravatar_id":"","url":"https://api.github.com/users/Stevenic","html_url":"https://github.com/Stevenic","followers_url":"https://api.github.com/users/Stevenic/followers","following_url":"https://api.github.com/users/Stevenic/following{/other_user}","gists_url":"https://api.github.com/users/Stevenic/gists{/gist_id}","starred_url":"https://api.github.com/users/Stevenic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Stevenic/subscriptions","organizations_url":"https://api.github.com/users/Stevenic/orgs","repos_url":"https://api.github.com/users/Stevenic/repos","events_url":"https://api.github.com/users/Stevenic/events{/privacy}","received_events_url":"https://api.github.com/users/Stevenic/received_events","type":"User","site_admin":false},"created_at":"2018-04-17T02:05:20Z","updated_at":"2018-04-17T02:05:20Z","author_association":"CONTRIBUTOR","body":"You can now very easily spin up a new `TurnContext` that you can pass directly to your middleware. Closing and please comment if the current code doesn't satisfy this need."}}
{"actor":{"display_login":"Stevenic"}}