Joke Fetch Lambda Function Code / How To Mock Reqwest Get?

Hi all! Here is some code that fetches a random joke from this open API!

Unfortunately, I haven't been able to run it locally yet. haha...

Also, I am wondering if anyone has any tips on how to implement this unit test. I want to somehow mock the request get call so that it doesn't actually make an API call during the test but instead returns some dummy data. thanks! :pray:

use lambda_http::{handler, lambda, Context, IntoResponse, Request};
use serde_json::json;
use reqwest;

type Error = Box<dyn std::error::Error + Sync + Send + 'static>;

#[tokio::main]
async fn main() -> Result<(), Error> {
    lambda::run(handler(hello)).await?;
    Ok(())
}

async fn hello(_: Request, _: Context) -> Result<impl IntoResponse, Error> {
    
    let joke = reqwest::get("https://v2.jokeapi.dev/joke/Any?safe-mode").await?;

    Ok(json!(joke))
}



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

    #[tokio::test]
    async fn hello_handles() {
        let request = Request::default();
        let expected = json!({
            "message": "Go Serverless v1.0! Your function executed successfully!"
        })
        .into_response();
        let response = hello(request, Context::default())
            .await
            .expect("expected Ok(_) value")
            .into_response();

        // TODO - mock the reqwest get so that it doesn't actually make an api call and mock the response...

        assert_eq!(response.body(), expected.body())
    }
}

In the function body you can use attributes to have it behave differently based on the compiler profile being used, like the one for tests. Conditional compilation is pretty straightforward, having a “test” block and a “not test” block is easy to read. In this case I might read a mock response from a file if it’s too big to have in the code.

You just have to be sure the behavior being mocked adequately suits the purpose of the test. If you want to test how the response is used (and not whether or not the API server is working) then a mock response is perfect. Since you’re not testing your own API but using someone else’s you could just curl the API and direct the real output to a mock data file.

Thanks @Rustaceous !

I get what you mean by curling the endpoint and pointing that response into a file... makes sense.

I'm not really following what you are suggesting to do with the "conditional compilation" though.

What exactly do I need to change in my cargo.yaml or elsewhere in the project to achieve what I'm trying to do?

If you’re looking to fake the remote endpoint I recommend checking out wiremock

1 Like

Example of what I had in mind below. Basically the idea is to tell the compiler that the function has a different body depending on the compiler profile, which is done with attributes. Configuring the profiles beyond the defaults is done in Cargo.toml, but you don't need to mess with that now.

This shows how you might get a real response when you're not testing but do something completely different (like reading it from a file instead) when you are testing. That way your tests don't do anything network-related that might affect rate limits.

If you run this as "Run", then run it with "Test" (i.e. "cargo run" and "cargo test", respectively) you'll see that the compiler ignores the item following #[cfg(test)] unless the "test" profile is used. In this case the item is a block that returns mock data. When this block is ignored, the function performs its logic as it should in production.

1 Like

@06chaynes I will check out wiremock. seems like a good lib for this. thanks!

@Rustaceous thanks for making this little project for me. :pray:

I am kind of skeptical about putting these #[cfg(test)] directives right in the code because to me it seems like your test is no longer testing that your function works!!

For example, you could put a bug in line 21 and return Ok(())

Then there is a bug in the code but the test will still pass...

No problem, I use it in a couple libraries for testing myself. If you’re interested in checking out an implementation feel free to check out some of the tests I’ve written using it here. There’s a few static variables and helper functions in the lib file of that crate, as well as a file for tests with reqwest and surf.

1 Like

The get_response function is not being tested. Your tests don't need to cover the external API, so as long as the dummy data is good and can be read the use_response function will still be tested. Separating use_response and get_response makes it clear which one is an external interface that doesn't get unit tests and which one is internal logic and should be covered by tests.

@Rustaceous huh? get_response is your function, not an external API... no reason it should not be tested.

You said you were getting a response from an external API.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.