workers-rs icon indicating copy to clipboard operation
workers-rs copied to clipboard

Unit testing

Open Suyashtnt opened this issue 2 years ago • 3 comments

How can you unit test workers? There should be an offical way to create tests and run them. Maybe run them via wranger test

Suyashtnt avatar Apr 24 '22 11:04 Suyashtnt

This is something I've been meaning to think of a solution to for a while, but haven't had a satisfactory idea on how we'd go about it. Today we announced our intent to open source the workers runtime, so I think once that happens we should build a cargo test-runner around the runtime so we can do this.

zebp avatar May 09 '22 22:05 zebp

Heya, just checking in on this -- I'm interested in covering a worker I'm working on with unit tests too!

Gogopex avatar Dec 07 '22 15:12 Gogopex

Showing my support for this! Definitely needed to easily create production-grade systems on Cloudflare with Workers.

I tried to use #[wasm_bindgen_tests], both with Node and Headless Chrome but had trouble getting it working and decided to give up after a few hours. With Node, the trouble is a mixing of ESM and CommonJS modules that doesn't play nice. For Headless Chrome, it runs—however, fetch() doesn't work properly and the error message isn't super helpful, just that the host is unreachable. Tried to debug it for a while, but nothing fruitful.

Edit: https://github.com/cloudflare/workers-rs/issues/317 turns out this fixes #[wasm_bindgen_tests] with Node, so I think documenting this approach would be an appropriate next step before we can have #[cloudflare_worker_tests]. You still can't run the router directly, because there isn't a way to initialize the context and so on appropriately—so you still need wrappers, but at least you don't need another runtime and wrapping in http::{Response,Request} manually.

Edit 2: https://github.com/cloudflare/workers-rs/pull/311 breaks deploys right now...

To unblock myself, I decided to use the tokio async runtime to run tests locally until there's upstream support for this. However, this presents some issues too, because Env/Request/Response is not properly public to the extent you can easily test with them. In addition, they depend on web_sys/js_sys features that aren't present outside of a WASM environment. This means you have to isolate your code and wrap everything in generic types like http::{Response,Request}. But testing the router isn't really possible with the current signatures, as far as I can gather. This creates even more indirection and copying than what's already present in WASM for Rust, which is a bit frustrating..

Edit: I see https://github.com/cloudflare/workers-rs/pull/286 which would greatly help with testability as well.

As a temporary solution, this is what I landed on that may help others:

async fn do_stuff(data: &DataType, req: &mut http::Request<String>, id: &str) -> http::Response<Vec<u8>> {
    # do things
	  http::Response::builder()
        .status(200)
        .body(body)
        .unwrap()
}


async fn main(...) {
       router
        .post_async("/whatever/:id", |mut req, ctx| async move {
            let id = ctx.param("id").unwrap();
            let url = req.url().unwrap();
            
            let mut request = http::Request::builder()
                .method("POST")
                .uri(url.as_str())
                .body(req.text().await.unwrap())
                .unwrap();

            let response = idx_search(&ctx.data, &mut request, id).await;
            let headers = response.headers().clone();
            let status_code = response.status().as_u16();

            Ok(Response::from_bytes(response.into_body())
                .unwrap()
                .with_headers(headers.into())
                .with_status(status_code))
        }).run(req, env).await
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_idx_search() {
        let mut req = http::Request::builder()
            .method("POST")
            .uri("http://localhost:8787/whatever/test-id")
            .body(String::from("[1.0]"))
            .unwrap();

        let result = do_stuff(&data_thingy, &mut req, "test-id").await;

        println!("{}", String::from_utf8(result.body().to_vec()).unwrap());
    }
}

The ideal solution is that all this runs within the Cloudflare worker environment, not sure if that's easier or harder than fixing either the tokio or wasm_bindgen path :)

sirupsen avatar May 23 '23 11:05 sirupsen