diff --git a/package.json b/package.json
index 325ed674464fa30296eb35b0cdc11b673875047d..7828beb92098cf513a0e608407411ceeb0650527 100644
--- a/package.json
+++ b/package.json
@@ -4,11 +4,11 @@
   "description": "A bridge between Matrix and Discord",
   "main": "discordas.js",
   "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1",
+    "test": "npm run-script build && mocha --opts test/mocha.opts build/test",
     "lint": "tslint --project ./tsconfig.json",
-    "coverage": "istanbul --include-all-sources cover -x /src/discordas.js _mocha -- test/ -R spec",
-    "build": "tsc",
-    "start": "tsc && node ./build/discordas.js -p 9005 -c config.yaml",
+    "coverage": "istanbul --include-all-sources cover -x build/src/discordas.js _mocha -- build/test/ -R spec",
+    "build": "rm -r build/* && tsc",
+    "start": "build && node ./build/discordas.js -p 9005 -c config.yaml",
     "getbotlink": "node ./tools/addbot.js"
   },
   "repository": {
@@ -34,17 +34,20 @@
     "discord.js": "^11.0.0",
     "js-yaml": "^3.8.1",
     "marked": "^0.3.6",
+    "matrix-appservice-bridge": "^1.3.5",
     "mime": "^1.3.4",
     "npmlog": "^4.0.2",
     "tslint": "^4.4.2",
-    "typescript": "^2.1.6",
-    "matrix-appservice-bridge": "^1.3.5"
+    "typescript": "^2.1.6"
   },
   "devDependencies": {
+    "@types/chai": "^3.4.35",
+    "@types/mocha": "^2.2.39",
     "chai": "^3.5.0",
     "chai-as-promised": "^6.0.0",
     "eslint": "^3.8.1",
     "istanbul": "^0.4.5",
-    "mocha": "^3.1.2"
+    "mocha": "^3.1.2",
+    "proxyquire": "^1.7.11"
   }
 }
diff --git a/src/discordbot.ts b/src/discordbot.ts
index 0bbf5f12107950c5cae47b3effe9adedfe13080e..28712e5812ffa0821a45ea009f0a99caa0405281 100644
--- a/src/discordbot.ts
+++ b/src/discordbot.ts
@@ -3,6 +3,7 @@ import * as Discord from "discord.js";
 import * as log from "npmlog";
 import { MatrixUser, RemoteUser } from "matrix-appservice-bridge";
 import { Util } from "./util";
+import * as Bluebird from "bluebird";
 import * as mime from "mime";
 import * as marked from "marked";
 
@@ -16,20 +17,18 @@ export class DiscordBot {
     this.bridge = bridge;
   }
 
-  public run () {
-    this.bot = new Discord.Client();
-
-    this.bot.on("ready", () => {
-      log.info("DiscordBot", "I am ready!");
-    });
-
+  public run (): Promise<null> {
+    this.bot = Bluebird.promisifyAll(new Discord.Client());
     this.bot.on("typingStart", (c, u) => { this.OnTyping(c, u, true); });
     this.bot.on("typingStop", (c, u) => { this.OnTyping(c, u, false); });
     this.bot.on("userUpdate", (_, newUser) => { this.UpdateUser(newUser); });
     this.bot.on("channelUpdate", (_, newChannel) => { this.UpdateRoom(<Discord.TextChannel> newChannel); });
     this.bot.on("presenceUpdate", (_, newMember) => { this.UpdatePresence(newMember); });
     this.bot.on("message", this.OnMessage.bind(this));
+    const promise = (this.bot as any).onAsync("ready");
     this.bot.login(this.config.auth.botToken);
+
+    return promise;
   }
 
   public GetBot (): Discord.Client {
@@ -40,14 +39,15 @@ export class DiscordBot {
     const guild = this.bot.guilds.find((g) => {
       return (g.id === server || g.name.toLowerCase().replace(/ /g, "-") === server.toLowerCase());
     });
-    if (guild === null) {
+    if (!guild) {
       return Promise.reject(`Guild "${server}" not found`);
     }
 
     const channel = guild.channels.find((c) => {
       return ((c.id === room  || c.name.toLowerCase() === room.toLowerCase() ) && c.type === "text");
     });
-    if (channel === null) {
+
+    if (!channel) {
       return Promise.reject(`Channel "${room}" not found`);
     }
     return Promise.resolve(channel);
@@ -205,7 +205,7 @@ export class DiscordBot {
   }
 
   private OnTyping(channel: Discord.Channel, user: Discord.User, isTyping: boolean) {
-    this.GetRoomIdFromChannel(channel).then((room) => {
+    return this.GetRoomIdFromChannel(channel).then((room) => {
       const intent = this.bridge.getIntentFromLocalpart(`_discord_${user.id}`);
       intent.sendTyping(room, isTyping);
     });
diff --git a/test/test_discordbot.ts b/test/test_discordbot.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a1a683fdeb54e3f2d4ed274d0af0f891af0b85c4
--- /dev/null
+++ b/test/test_discordbot.ts
@@ -0,0 +1,187 @@
+import * as Chai from "chai";
+import * as ChaiAsPromised from "chai-as-promised";
+import * as Proxyquire from "proxyquire";
+import { DiscordBridgeConfig } from "../src/config";
+
+Chai.use(ChaiAsPromised);
+
+const assert = Chai.assert;
+const should = Chai.should as any;
+class DiscordClient {
+  public guilds: any;
+  private testLoggedIn: boolean = false;
+  private testCallbacks: Array<() => void> = [];
+  constructor() {
+    let channels = [
+      {
+        id: "321",
+        name: "achannel",
+        type: "text",
+      }, {
+        id: "654",
+        name: "a-channel",
+        type: "text",
+      }, {
+        id: "987",
+        name: "a channel",
+        type: "text",
+      },
+    ];
+    let guilds = [
+      {
+        id: "123",
+        name: "MyGuild",
+        channels,
+      },
+      {
+        id: "456",
+        name: "My Spaces Guild",
+        channels,
+      },
+      {
+        id: "789",
+        name: "My Dash-Guild",
+        channels,
+      },
+    ];
+    this.guilds = guilds;
+
+  }
+
+  public on(event: string, callback: () => void) {
+    if (event === "ready") {
+      this.testCallbacks[0] = callback;
+    }
+  }
+
+  public login(token: string) {
+    this.testLoggedIn = true;
+    this.testCallbacks[0]();
+  }
+}
+
+const mockDiscord = {
+  Client: DiscordClient,
+};
+
+const mockBridge = {
+  getRoomStore: () => {
+    return {
+      getEntriesByRemoteRoomData: (data) => {
+        if (data.discord_channel === "321") {
+          return Promise.resolve([{
+            matrix: {
+              getId: () => {return "foobar:example.com"; },
+            },
+          }]);
+        }
+        return Promise.resolve([]);
+      },
+    };
+  },
+  getIntentFromLocalpart: (localpart: string) => {
+    return{
+      sendTyping: (room: string, isTyping: boolean) => {
+        return;
+      },
+    };
+  },
+};
+
+const modDiscordBot = Proxyquire("../src/discordbot", {
+  "discord.js": mockDiscord,
+});
+
+describe("DiscordBot", () => {
+  const config = {
+    auth: {
+      botToken: "blah",
+    },
+  };
+  describe("run()", () => {
+    it("should start ok.", () => {
+      const discordBot = new modDiscordBot.DiscordBot(
+        config,
+        mockBridge,
+      );
+      assert.doesNotThrow(discordBot.run.bind(discordBot));
+    });
+    it("should resolve when ready.", () => {
+      const discordBot = new modDiscordBot.DiscordBot(
+        config,
+        mockBridge,
+      );
+      return discordBot.run();
+    });
+  });
+  describe("LookupRoom()", () => {
+    const discordBot = new modDiscordBot.DiscordBot(
+      config,
+      mockBridge,
+    );
+    discordBot.run();
+    it("should reject a missing guild.", () => {
+      return assert.isRejected(discordBot.LookupRoom("MyMissingGuild", "achannel"));
+    });
+
+    it("should resolve a guild.", () => {
+      return assert.isFulfilled(discordBot.LookupRoom("MyGuild", "achannel"));
+    });
+
+    it("should resolve a guild with an id.", () => {
+      return assert.isFulfilled(discordBot.LookupRoom("123", "achannel"));
+    });
+
+    it("should resolve a guild with spaces.", () => {
+      return assert.isFulfilled(discordBot.LookupRoom("My-Spaces-Guild", "achannel"));
+    });
+
+    it("should resolve a guild with dashes.", () => {
+      return assert.isFulfilled(discordBot.LookupRoom("My-Dash-Guild", "achannel"));
+    });
+
+    it("should reject a missing channel.", () => {
+      return assert.isRejected(discordBot.LookupRoom("MyGuild", "amissingchannel"));
+    });
+
+    it("should resolve a channel with spaces.", () => {
+      return assert.isFulfilled(discordBot.LookupRoom("MyGuild", "a channel"));
+    });
+
+    it("should resolve a channel with dashes.", () => {
+      return assert.isFulfilled(discordBot.LookupRoom("MyGuild", "a-channel"));
+    });
+
+    it("should resolve a channel with an id.", () => {
+      return assert.isFulfilled(discordBot.LookupRoom("MyGuild", "321"));
+    });
+  });
+  describe("ProcessMatrixMsgEvent()", () => {
+
+  });
+  describe("UpdateRoom()", () => {
+
+  });
+  describe("UpdateUser()", () => {
+
+  });
+  describe("UpdatePresence()", () => {
+
+  });
+  describe("OnTyping()", () => {
+    const discordBot = new modDiscordBot.DiscordBot(
+      config,
+      mockBridge,
+    );
+    discordBot.run();
+    it("should reject an unknown room.", () => {
+      return assert.isRejected(discordBot.OnTyping( {id: "512"} {id: "12345"}, true));
+    });
+    it("should resolve a known room.", () => {
+      return assert.isFulfilled(discordBot.OnTyping( {id: "321"} {id: "12345"}, true));
+    });
+  });
+  describe("OnMessage()", () => {
+
+  });
+});
diff --git a/tsconfig.json b/tsconfig.json
index b51be7ecfed92da985567e893e71390904ef1347..c5651a0591da08bfac5cd8395e06276590a4c28c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,10 +5,12 @@
         "target": "ES6",
         "noImplicitAny": false,
         "sourceMap": false,
-        "outDir": "./build"
+        "outDir": "./build",
+        "types": ["mocha", "node"]
     },
     "compileOnSave": true,
     "include": [
-        "src/**/*"
+        "src/**/*",
+        "test/**/*"
     ]
 }