Testing the actor (Rust)
Now that your actor project is generated we can write a simple unit test to verify its functionality. We want to ensure that our logic to parse the query string for a name and return Hello <name>
is correct.
Right now, our actor is laid out in a way that is difficult to test properly. Let's look at the function signature inside of the impl
block:
async fn handle_request(
&self,
_ctx: &Context,
req: &HttpRequest,
) -> std::result::Result<HttpResponse, RpcError> {
let text = form_urlencoded::parse(req.query_string.as_bytes())
.find(|(n, _)| n == "name")
.map(|(_, v)| v.to_string())
.unwrap_or_else(|| "World".to_string());
Ok(HttpResponse {
body: format!("Hello {}", text).as_bytes().to_vec(),
..Default::default()
})
}
This function accepts &self
and &Context
arguments which are unused for our business logic. Additionally, the function is async
which requires a separate dependency to call this function from a test. We can remediate these issues with just a little bit of refactoring.
/// Implementation of HttpServer trait methods
#[async_trait]
impl HttpServer for HelloActor {
/// Returns a greeting, "Hello World", in the response body.
/// If the request contains a query parameter 'name=NAME', the
/// response is changed to "Hello NAME"
async fn handle_request(
&self,
_ctx: &Context,
req: &HttpRequest,
) -> std::result::Result<HttpResponse, RpcError> {
handle_http_request(req)
}
}
fn handle_http_request(req: &HttpRequest) -> std::result::Result<HttpResponse, RpcError> {
let text = form_urlencoded::parse(req.query_string.as_bytes())
.find(|(n, _)| n == "name")
.map(|(_, v)| v.to_string())
.unwrap_or_else(|| "World".to_string());
Ok(HttpResponse {
body: format!("Hello {}", text).as_bytes().to_vec(),
..Default::default()
})
}
Moving the bulk of our business logic to a synchronous, stateless function makes it much easier to test. Now that we've done the above refactor, we can paste the following code at the bottom of the file:
#[cfg(test)]
mod test {
use crate::handle_http_request;
use std::collections::HashMap;
use wasmcloud_interface_httpserver::{HttpRequest, HttpResponse};
#[test]
fn can_handle_request() {
let request = HttpRequest {
method: "GET".to_string(),
path: "/".to_string(),
query_string: "name=test".to_string(),
header: HashMap::new(),
body: vec![],
};
let response: HttpResponse = handle_http_request(&request).unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(
String::from_utf8(response.body).unwrap(),
"Hello test".to_string()
);
}
}
If you're new to Rust, the #[cfg(test)]
and #[test]
annotations are special markers that denote this module and function as a test function, so they will not be compiled into your WebAssembly module.
This test can_handle_request
constructs an HttpRequest
object and passes it to our handle_http_request
function, asserting that the HttpResponse
object has a successful status code and contains our intended Hello test
body.
To run this test, use the following command:
- Unix
- Windows
- Other
# Override wasm32-unknown-unknown with default rustc target
cargo test --target=$(rustc -vV | awk '/host:/ {print $2}')
# Override wasm32-unknown-unknown with default rustc target
cargo test --target=$(rustc -vV | Select-String "host" | Out-String | ForEach-Object { $_.Split(':')[1] })
# e.g. cargo test --target=x86_64-unknown-linux-gnu
cargo test --target=<your_native_target>