Adding tests to your Discord Bot - Discord Bot Series (Part 3)
Tags: kotlin, javascript, discord, kotlinjs
Published: 2022-07-06 16:00:53

Discord is a great platform for keeping in touch with friends, and adding a bot can enrich that experience. A bot can respond to messages, perform an action based on a command, or even add music to a voice chat.

In this series we'll be going over how to create a small example bot by using the NPM module for DiscordJS in a KotlinJS project.

**Note**: This is the third post in a series of three posts, about how to use KotlinJS and NPM modules to create a full fledged project, in this case a Discord Bot.

## Previously

In my [last post](https://dev.to/kevinschildhorn/using-kotlinjs-and-npm-to-create-a-discord-bot-discord-bot-series-part-2-1n6e) we went over how to add DiscordJS to our project, and start creating our bot. We added a listener for messages and replied to "hello" with a response of the computer name. I mentioned at the end that we weren't catching some edge cases with our response, and so in this post we'll be adding tests to our discord bot.

As a refresher, in this series we're going to create a small discord bot that responds to a specific message with its computer name.

### Creating tests in kotlin

This article assumes you understand some basics about adding tests in a kotlin project, specifically using the `kotlin.test` library. You can see a helpful guide [from jetbrains](https://www.jetbrains.com/help/idea/tdd-with-kotlin.html).

## Adding testing dependencies

First off, we need to add dependencies to testing libraries. Make sure you have these dependencies in your `build.gradle` file.

```kotlin
dependencies {
    testImplementation(kotlin("test"))
    testImplementation(kotlin("test-js"))
}
```

## Testing our logic

In the [last post](https://dev.to/kevinschildhorn/using-kotlinjs-and-npm-to-create-a-discord-bot-discord-bot-series-part-2-1n6e) we added logic to respond to "hello" messages from users in the channel. We now want to test and make sure our bot says hello only when it gets a correct message. We want to have them ignore capitalization and not respond to other bots (I mean they can if they want, this is just for the example). Feel free to add any other requirements you want!

## Separating out code

As it is now, we cannot test our code as it's just sitting inside the "messageCreate" callback. First we should move our code to a separate file. Let's make a new class called `HelloHandler`:

```kotlin
class HelloHandler {
    fun handleMessage(message: Message){
        if(message.content == "hello")
            message.channel.send("...")
    }
}
```

Now that we've moved our code to the handler we can update the existing code like so:
```kotlin
    val helloHandler = HelloHandler()
    client.on("messageCreate") { message ->
        helloHandler.handleMessage(message)
    }
```

Now we can call this code from a test. Let's make a quick test for testing a successful response:

```kotlin
class HelloGreetingTest {

    @Test
    fun testHelloWorldSuccess() {
        val message = Message()
        val helloHandler = HelloHandler()
        helloHandler.handleMessage(message)
        // assertTrue{ /* TODO: Assert the message was sent */ }
    }
}
```

Hopefully this seems straight forward, though you may notice that we  aren't asserting anything at the moment. This is because we don't have a way to track that a message has been sent, and we don't really want that in our `HelloHandler`. For now it's commented out but we will update this in a later section.

### Custom Messages

Another thing you may notice is there's no way to set the content of the `Message`, this is because the `Message` class is defined externally, which means that the bot is expecting the definition from the external Js. 

> Even if we didn't want a custom message, you cannot use external classes in testing, because there are variables that are expected but undefined

So how do we get around this? 

#### Interfaces

The easiest way I've found to test external classes is to create a common interface. Then we can have two classes implementing this interface: the external class we already have, and a mocking class. Let's create an interface for `Message`:

```kotlin
external interface MessageInterface {
    val author: UserInterface
    val channel: ChannelInterface
    val content: String
}
```

Here we are defining the values we want to test, and we are setting it as external to match the existing `Message` class. 
> **Note**: You don't need to define values you don't need, or else your interface would be massive for no good reason.

Now we'll have:

```kotlin
external class Message : MessageInterface {
    override val author: User
    override val channel: TextChannel
    override val content: String
}

data class MockMessage(
    override val author: MockUser,
    override val channel: MockChannel,
    override val content: String
) : MessageInterface
```

I've gone ahead and created a `MockUser` and a `MockChannel`,  as we want to check the username for a bot name and check if the message was sent. These were created the same as the `MockMessage`, by creating an interface and implementing it.
> Note that all other `Mock` classes should be referencing the Interfaces you've created, not the external classes. *Don't forget* to update the `HelloHandler` to use the interface.

So now we can test a custom message, great! How do we make sure our bot sent a greeting as a response? 

### Checking Responses

In order to test a response to an incoming message, we can create a `MockChannel` to track when messages are sent. We don't want to actually send a message, just keep track of its status. I've created a `MockChannel` that has a simple `Boolean` that tells whether a message was sent.

```kotlin
class MockChannel : ChannelInterface {

    var sentMessage: Boolean = false
        private set

    override fun send(content: Any): Promise<MessageInterface> {
        print("Sent Message!")
        sentMessage = true
        return Promise { _, _ -> }
    }
}
```

So now we can assert that the `sentMessage` variable is true or false, depending on our test. You can go a step further and keep a reference to the information for other tests, but for simplicity we'll stick to this. Now we can finally go back to the test function and update it with our new code.

### Final Test

```kotlin
    @Test
    fun testHelloWorldSuccess() {
        val user = MockUser("Kevin")
        val channel = MockChannel()
        val message = MockMessage(user, channel, "Hello")

        val helloHandler = HelloHandler()
        helloHandler.handleMessage(message)
        assertTrue{ message.channel.sentMessage }
    }
```

Here we have a working test. We create the mock information, create the Handler, then handle the message. Then we assert that the bot sent a message back! Now let's run the test, by either clicking the arrow next to the test and selecting `run`, or calling `./gradlew test` from the command line.

It failed!

Why did it fail? Let's check the `HelloHandler`:
```kotlin
    if(message.content == "hello")
        message.channel.send("...")
```
It looks like we didn't take capitalization into account. Lets quickly change the check to `message.equals("hello", ignoreCase = true)`, and run again.

It passed!

Excellent, our bot is working as we'd expect. We can go ahead and add more tests to make sure that other messages don't trigger a response, and catch other edge cases. Maybe we want to include exclamation points, or other languages, or we want to ignore hellos from users named Kevin. The possibilities are endless.

## Conclusion

Congrats, You've tested your Discord Bot! You now have the basics to test all different messages and how your bot responds to them.

Your bot should now be running, stable, and ready to join some channels. This is the last post of the series, at this point you should have everything you need to know to be able to run a simple Discord Bot in KotlinJs. There are many other references out there to expand your bot from here using KMP libraries. Hopefully this series has been helpful and given you an introduction into kotlinJs.

Thanks for reading! Let me know in the comments if you have questions. Also, you can reach out to me at [@kevinschildhorn](https://twitter.com/KevinSchildhorn) on Twitter.