#mock unit-integration testing
99 messages · Page 1 of 1 (latest)
mock unit-integration testing
What I always wondered, is whether we need this at all.
If you use a proper testing framework, you'll have access to not only mocks, but also spies that are able to intercept calls and allow you to assert both input and output, something more powerful and more convenient than reverse-engineering Discord (which is against Discord's ToS), as you can also test if you're passing the right instances and not just clones.
And of course, you can also use this exact same system, not only for discord.js, but also for your bot's code.
An example of spies in Jest: https://jestjs.io/docs/jest-object#jestspyonobject-methodname.
What's more handy for me is an easy way of creating mocks without needing to specify all the data, for which I just create a template object and then use object spread to assign custom values for tests. What could be improved in discord.js's side is that we could type the payloads each structure's constructor accepts, as opposed to just unknown.
Which I am going to do probably later today actually, been planning on it for a bit
So is a REST/gateway server useful for this?
Not at all
You can completely avoid those by mocking your d.js calls
const channelSendSpy = jest.spyOn(channel, 'send'); and then you run operations on it. When you do channel.send(), it'll call whichever implementation you passed on channelSendSpy.mockImplementation(...), then you can assert whether the parameters and the returned data was right.
alrighty, here's the pr
#6230 in discordjs/discord.js by ckohen opened <t:1627636887:R> (review required)
types: use api-types for stricter typings
📥 npm i ckohen/discord.js#stricter-typings
How are people making mock structures?
Also what is this bot? 👀
discord-api-types to type the raw data as the one Discord sends us
So you have to create the entire hierarchy just to make a mock message structure 🤔
Not necessarily
You can test a partial DM message, works fine for most cases
Unless you're testing stuff that requires a guild, or the channel, then in that case... yeah, you gotta mock more structures
Yeah, I'm migrating a ton of existing tests from Eris, for which we never really established the best structure for
currently they're all breaking because the logging facilities log the channel snowflake
I'm unsure what you're referring to with logging facility
I think I can work around it with channelId instead of channel.id
Ah, yeah
As in we're logging the channel and message snowflakes and some other metadata with each incoming command
As a side note, our plans for v14 will probably make mocking enormously easier, since we'll store a good chunk of the raw data
But even so I have to consider if the approach we have is right because there's a stupid amount of boilerplate and they're really flaky
Aha, that's exciting 🙂
And use getters to decouple structures from state
Unrelated, but can you tell me what that GitHub bot is with the slash command? I think it might be useful in some of our servers
Lemme check if I can find it
Oh damn, even CloudFlare has a Lambda competitor
So it's self-hosted
Yeah, a free one and pretty good
Souji is staff here
It's this cutie
Neat
It's hard to get word in here because the channels are always flooded with mostly questions about programming fundamentals... the woes of popularity XD
XD
I'll ask something else less on topic for this thread: is it a design choice of Discord.js for almost all actions to be invoked as methods on object instances? Even when they might correspond directly to a single REST API call?
And we might accidentally kill all competition once we finish our raw components
wdym
For example, to send a message, I need to fetch the channel object first. To leave a server, I need to fetch the guild object first.
It's called OOP, and it's part of SOLID
Under Eris, the Client object already has createMessage and leaveGuild methods, mapping one-to-one to REST APIs, since you don't actually need to make a pre-request to fetch all the data to construct the channel and guild objects to perform the action.
It was a lot worse in the past, you had to fetch the message in order to edit or delete it, we now have those methods in channel.messages so we can do those operations without cache (since v13)
Yeah, I know
So it is a design choice
I don't think there are plans at all to one-up that hierarchy, buuuuuuuuuut
I did hear from the channel in the API server about @discordjs/rest being spun off, so that might help for some one-offs
GuildManager#leave could exist, that's not too hard to do
Once we implement the full new stack (discord.js in TS), we might need to expose the raw REST handler
Oh that's sick
And by then we'll also have pretty convenient builders to create the data it accepts
That makes it more like Detritus doesn't it
It's what I meant with us possibly killing all competition in the future, our plans for d.js's future are pretty bright
but then you'll have people asking about TypeScript fundamentals too
We plan on making @discordjs/rest very complete, supporting custom ratelimit cache strategies (hint: Redis)
👀
~~why are reactions disabled in this server 😭 ~~
At the same time we'll soon also work on @discordjs/ws, with opt-in internal worker sharding
So if you want to go full raw, you'd ideally use /rest + /ws
discord.js will just leverage all the components into a single, power-user oriented, package
Makes sense to be honest, I can't think of a reason for that particular case to not exist
I'm not actually into "full raw" (evidenced by my struggles with unit testing), just annoyed when an API that's meant to be a convenience is cumbersome or less efficient
If you ask me, KODER
The inefficiency of discord.js for mocking is enormously outweighed by the slowness of Jest
Okay, in that context I meant inefficiency in terms of Discord API calls made (this was referring to leaving guilds and sending messages)
Inefficiency in terms of mocking is just how long the tests take to write and how complex they are to set up
You... don't do Discord API calls in test suites
For testing, it's more about my developer time, not how long they take to run on CI, unless we start wasting tens of minutes or worse
Use something like this to mock HTTP calls https://mswjs.io/
We're talking two things at once and it's confusing 😅
my gripe with this has nothing to do with my earlier question about unit testing
I mean... for the test suite, I can just keep adding one-line helpers to construct data of one type
You can see how I can construct a User instance with just createUser(), a role with createRole(), and so on
Yeah, I'm going to try it out, this helps
Found the place I'm using them at: https://github.com/skyra-project/skyra/blob/main/tests/lib/database/settings/structures/PermissionNodeManager.test.ts
So embeds and attachments are instances of classes in Discord.js and not just plain old objects. In a test where Message::reply has been stubbed out, how do you make assertions on them?
Embeds and attachments are instances of classes that users can construct easily, since they're what we call, constructor classes
As for stubbing Message#reply, you have 2 ways for that:
1.- Using spies: jest.spyOn(message, 'reply').mockImplementation(...), very powerful, but can be repetitive and skips a lot of discord.js's logic, not ideal if you want to do e2e integration testing, also dependent of a testing suite, which may not be ideal
2.- Using HTTP mocks: A lot lower level, can be tricky in some specific cases, but overall can be a lot easier (as easy as creating mock data for most cases, just like I'm doing in my tests), using the library I linked before (https://mswjs.io/)
(You can use nock too if you want)
Bonus part, @discordjs/rest uses Nock for testing ratelimits, you can check https://github.com/discordjs/discord.js-modules/blob/main/packages/rest/__tests__/RequestHandler.test.ts for an example of them, although we'll switch to MSWJS soon
Situation: Message#reply is already stubbed. The command in question is going to reply with an embed or an attachment. Under Eris, I either had an assertion on a specific nested property or a deep comparison since it was a plain object. Here I don't think a deep comparison works since it's not a plain object. The property assertion is rather messy with the typing of the reply arguments I think. I'm trying something like
const replySpy = this.stub(sampleMessage, "reply").resolves();
await command(sampleMessage):
const reply = replySpy.args[0][0];
expect(reply).to.have.property("embeds");
if (typeof reply === "object" && "embeds" in reply) {
expect(reply.embeds?.[0]?.fields?.[1]?.value).to.equal("Some specific thing");
}
You can mix the two things I shared btw, although the first makes the second less useful since you can't check for the arguments that were passed
Is that... Jasmine?
If you're passing a MessageEmbed, you can use another to compare
mocha, chai, sinon ecosystem, both jest and mocha have Jasmine syntax
Otherwise, if what you're doing is comparing MessageEmbed vs raw data, do either me.toJSON() or new MessageEmbed(raw)
And the same is true for attachments?
Yes
Attachments are a bit more complicated
The data you send is different from the data you receive, the file contents are defined in the former (for obvious reasons) while missing in the latter
I don't think you can really do that for attachments, now that I think of it, unless you have the raw data
Well I will try here now... Is there a channel I can ask about unit tests? Of a bot, not the library. It is proving quite a bit more difficult with slash commands as I pretty much need CommandInteraction objects. Is there a way to create one through the library with particular values? Can I "new" one somehow?
There's literally a ton of information about that in this channel, please read the previous messages, @empty merlin
I surely will. I didn't know that this was a "thread" and/or how it worked.
The messages seem to indicate (at least) two layers of unit testing. I wasn't so very interested in testing parts of discord.js as I assume any errors there would be found by testers of the library. Not entirely unlike our relying on JS or Node or the .Net libraries to work according to spec. My testing system for message commands worked very well precisely because the values needed for testing were parsed out of a string and passed along to functions as primitives. Simple to generate dozens of them for tests. One thought I had (but I am very reluctant to write my functions this way) is to separate the "function" into 2 parts. If there are "interaction aware" and "primitive value aware" functions then the unit tests could test the primitive value ones basically trusting that something like interaction.options.getString('msg') would return a string (or discord.js is broken).
I'd be interested how other expose private functions for testing but this system I read about works well for me. It obviously doesn't make the function private if someone is intent on calling it but it makes it very well known that calling it isn't intended. So for example: