dismock

Dismock is a library that aims to make mocking Discord's API requests as easy as winking.
No more huge integration tests that require a bot on some private server with little to no debug information.
Dismock is not limited to a specific Discord library, although it uses arikawa as a foundation for its datatypes.
Getting Started
Basic Testing
Creating a mock is done, by calling the respective mock method, that belongs to the API request you made in your code.
Below is a basic example of a ping command and it's unit test.
func (b *Bot) Ping(e *gateway.MessageCreateEvent) (error) {
_, err := b.Ctx.SendText(e.ChannelID, "๐")
if err != nil {
return err
}
_, err := b.Ctx.SendText(e.ChannelID, "Pong!")
return err
}
func TestBot_Ping(t *testing.T) {
m, s := dismock.NewState(t)
// if you want to use a Session and no State write:
// m, s := dismock.NewSession(t)
var channelID discord.ChannelID = 123
m.SendText(discord.Message{
// from the doc of Mocker.SendText we know, that ChannelID and Content
// are required fields, all other fields that aren't used by our function
// don't need to be filled
ChannelID: channelID,
Content: "๐",
})
// make sure that API calls on the same endpoint with the same method are
// added in the correct order
m.SendText(discord.Message{
ChannelID: channelID,
Content: "Pong!"
})
b := NewBot(s)
b.Ping(&gateway.MessageCreateEvent{
Message: discord.Message{
ChannelID: channelID,
}
})
// at the end of every test Mocker.Eval must be called, to check for
// uninvoked handlers and close the mock server
m.Eval()
}
Advanced Testing
Now imagine a bit more complicated test, that has multiple sub-tests:
func (b *Bot) Ping(e *gateway.MessageCreateEvent) (error) {
_, err := b.Ctx.SendText(e.ChannelID, "๐")
if err != nil {
return err
}
_, err := b.Ctx.SendText(e.ChannelID, e.Author.Mention()+" Pong!")
return err
}
func TestBot_Ping(t *testing.T) {
m := dismock.New(t)
var channelID discord.ChannelID = 123
m.SendText(discord.Message{
ChannelID: channelID,
Content: "๐",
})
t.Run("test1", func(t *testing.T) {
// If you have multiple tests that have the same basic API requests,
// you can create a mocker, add those API calls, and create a clone
// of the mocker in every sub-test you have.
// Cloned mockers have a copy of it's parents request, but run their
// own mock server and have a dedicated Session/State.
m, s := m.CloneState(t)
...
})
t.Run("test2", func(t *testing.T) {
m, s := m.CloneState(t)
...
})
}
Using a Different Discord Library
Because the mocking is done on a network level, you are not limited to arikawa for mocking.
When creating a Mocker
just use dismock.New
and use the http.Client
of the mocker as client for a library of your choice.
m := dismock.New(t)
s, _ := discordgo.New("Bot abc") // the token doesn't have to be valid
s.Client = m.Client
That's it!
Besides regular calls to the API you can also mock requests for metadata, i.e. images such as guild icons (Mocker.GuildIcon
).
In order for this to work, you need to use the http.Client
found in the Mocker
struct, so that the mock server will be called, instead of Discord.