Usage Guide
So you're interested in how to use @rbxts/expect
?
This page will run you through some of the core concepts of @rbxts/expect
, and give you an idea on the expected usage.
Matchers
Matchers are methods that perform checks on your actual value. They're the core functionality of @rbxts/expect
.
import { expect } from "@rbxts/expect";
expect([1, 2, 3]).to.not.be.empty();
// empty is a matcher
Depending on your preference, you can either:
- Look through the matchers category for a compilation of matchers, organized by their effect on certain types.
- Look through the api reference to see all the available matchers.
Both have similiar documentation, the only difference is that the matchers category organizes a lot of the common matchers by type- where's the api reference has all the matchers in a single list.
Proxies
Proxies are effectively just wrappers around values; wrappers that allow @rbxts/expect
to track the path you access
(for error messages).
Expected parent.cars to be empty, but it had 2 elements.
parent.cars: '["Tesla","Civic"]'
Creating proxies
To create a proxy, you just need to call the createProxy method before you start your property chain.
import { expect, createProxy } from "@rbxts/expect";
const dad = {
name: "Dad",
age: 25,
cars: ["Tesla", "Civic"],
};
const person = {
name: "Daymon",
age: 24,
parent: dad,
};
expect(createProxy(person).parent.cars).to.be.empty();
Expected parent.cars to be empty, but it had 2 elements.
parent.cars: '["Tesla","Civic"]'
All the provided matchers come with variants of their error message when used alongside proxies.
Proxy callbacks
If you're wanting to make multiple checks on a single object, you can use the withProxy function, which will invoke your callback with the created proxy.
import { expect, withProxy } from "@rbxts/expect";
const dad = {
name: "Dad",
age: 25,
cars: ["Tesla", "Civic"],
};
const person = {
name: "Daymon",
age: 24,
parent: dad,
};
withProxy(person, (proxy) => {
expect(proxy.age).to.equal(24);
expect(proxy.parent).to.be.ok();
expect(proxy.parent.cars).to.be.empty();
});
Expected parent.cars to be empty, but it had 2 elements.
parent.cars: '["Tesla","Civic"]'
Proxies are ONLY intended to be used on the left side of checks (the "actual" value).
Using a proxy on the right side (the "expected" value) is undefined behavior and, depending on the method, may result in a warning to your console.
Config
@rbxts/expect
exports the ExpectConfig interface as a means for configuring certain
behaviors in your expect
calls.
export interface ExpectConfig {
/**
* The length at which collapsible values become collapsed variants.
*
* @defaultValue `20`
*
* @remarks
* When actual/expected values are beyond this length in size (as strings), they
* are converted into "collapsed" versions of themselves.
*
* Arrays are collapsed to `[...]`
* Objects are collapsed to `{...}`
* Strings are collapsed to `"..."`
* And everything else is collapsed to `'...'`
*
* @see {@link ActualPlaceholder.fullValue | fullValue}
*/
collapseLength: number;
}
Managing the default config
You can use the setDefaultExpectConfig and resetDefaultExpectConfig functions to update the default config.
import { expect, withProxy, setDefaultExpectConfig, resetDefaultExpectConfig } from "@rbxts/expect";
export = () => {
beforeAll(() => {
setDefaultExpectConfig({
collapseLength: 10,
});
});
afterAll(() => {
resetDefaultExpectConfig();
});
it("checks if the actual value is included in the array", () => {
expect(5).to.be.anyOf([1, 2, 3, 4, 5]).but.not.anyOf([1, 2, 3, 4]);
});
};
Since expect
uses the latest config whenever it needs a value, you don't need to provide the config anywhere in your
expect
calls.
Usage outside of tests
Since @rbxts/expect
is test-agnostic, you don't need to use it in a testing environment.
import { expect } from "@rbxts/expect";
import { remotes } from "./remotes";
import { saves } from "./saves";
import { pets } from "./data";
remotes.purchasePet.connect(async (player, petId) => {
const data = saves.get(player);
const pet = pets.get(petId);
expect(data.money).to.be.gte(pet.cost);
data.money -= pet.cost;
data.pets.push(pet);
data.save();
return "Pet purchased!";
});
You can also provide your own override for the message failure by providing a string as the second argument to expect.
expect(data.money, "You don't have enough money!").to.be.gte(pet.cost);
Client usage
Since @rbxts/expect
doesn't use any server-side services, you can use it on the client without any issues.
import { expect } from "@rbxts/expect";
import { pets } from "@shared/data";
import { LocalData } from "@client/save";
import { remotes } from "@shared/remotes";
export async function buyPet(petId: string) {
const pet = pets.get(petId);
expect(LocalData.money).to.be.gte(pet.cost);
return remotes.purchasePet(petId);
}
This way, you can also add a layer of your tech stack that validates state before sending server events; reducing overhead to your server.
While you can utilize @rbxts/expect
on the client, you should always have checks on the server too.
Rule #1 of Client-Server security: "Never. Trust. The. Client."
Next steps
If you're wanting to learn more, feel free to check out any of the following resources: