This would look something like this, in this case. It might move to India to be closer to my colleague. Let's look at what the code might look like for a durable object.
Fitting a collaborative editor into a slide is tricky, so let's stick with a simple request counter. Whenever a request comes in, we hash the URL into a durable object ID and we get a stub to that object and forward requests to it. The counter instance will increment and return its new count.
The important thing for us is that durable objects reuse the same request context for all requests and auto extend their lifetime. By executing the test run inside a single durable object, we don't need to worry about calling waitUntil. This is great for running requests like WebSocket connections. What we want is a durable object that terminates a WebSocket connection, imports the VTest worker script, and uses WebSocket messages for RPC.
This is a durable object that does that. We'll start by creating a WebSocket pair, which is a nonstandard workers API, kind of like MessageChannel, but for WebSockets. You keep one half and return the other half in the response. Then we extract the data for this worker from a header using a more advanced version of JSON.parse that supports structured types with circular references. We use a custom WebSocket message port class to adapt a WebSocket with a message port like interface. Again, with support for structured serializable types.
We're still trying to run code designed for Node here, but luckily we have this Node.js compatibility flag which enables support for a subset of Node's built-in modules and then by polyfilling a few more built-in modules with the module service, we're able to get VTest running in WorkerD. This enables us to run tests that import basic functions, call them, and assert on their return values, which is a great start, but what about those env and context parameters from earlier? How do we write tests that depend on them?
For this, we'll define a Cloudflare test module that gets returned by the module fallback service. We use a module-level variable for storing the current environment and export a function for setting this. Our Cloudflare test module reexports a subset of an internal module so users can't mess around with hidden things. The env variable is set using the second parameter to our runner objects constructor, and with that, we kind of have integration tests. We're able to import worker handles and call them directly.
Remember what I said about the request context earlier? The test runner is running inside a durable object context so we don't have to worry about wait and tills. Because we're calling worker handlers inside that context, they also don't have to worry about wait and tills, which isn't really what we want from an integration test. We'd like the behavior to match production. Instead, we allow users to configure a main entry point. In the worker implementing the runner object, we define a handler that imports the entry point with VT and forwards the call on to the handler.
Because we're importing with VT, we get hot module reloading for free. When main or one of its dependencies change, modules are invalidated and tests rerun with new code. We then define a worker binding to the current worker in the test module named self.
Comments