Testing HTTP requests issued by CLI

Hi,

I am writing a CLI for an HTTP API using reqwest. I would like to test/verify which HTTP requests are issued for some invocations of the CLI with various flags/options set.

I just want an automated way to verify things like: "when I issue cli subcmd -opt1 --option2 arg1 arg2 then then cli send http://api.endpoint.com/sub?key1=arg1..."

I guess I could use httpmock but it is kinda overkill for what I need no ?

Is there any crate that does that ?

thanks for your help

B

If you split up your CLI app into two functions, one that takes an iterator over OsString and gives the parsed output, and one that takes the parsed output and runs the actual code you can unit-test each of them separately.

Hi,

That is kinda what I do. My question relates to the testing of the second function. All it does it takes the cli arguments "parsed output" and issues various HTTP requests using the reqwest crate. I need a way to check which requests are issues: url? headers? body? ... based on what arguments are supplied.

I'd make the code generic over the thing sending HTTP requests. Then in the CLI app you can pass in an implementation backed by reqwest and during testing you pass in your dummy implementation.

fn send_requests<C>(client: &mut C, ...) where C: HttpClient {
  ...
}

trait HttpClient {
  fn send_request(&mut self, request: Request) -> Result<Response, Error>;
}

impl HttpClient for reqwest::Client { ... }

struct MockClient { requests: Vec<Request> }

impl HttpClient for MockClient { 
  fn send_request(&mut self, request: Request) -> Result<Response, Error> {
    self.requests.push(request);
    Ok(Response::success())
  }
}

Indeed, I could have my command line turns its argument into http_types::Request and validate them in the MockClient implementation of HttpClient.
I could switch from reqwest (who doesn't use http_types) to http_client::HttpClient.

Quick question though: how to I "plug in" the right HttpClient implementation ? I am familiar with dependency injection in Java but not in rust...

You don't really need a fancy DI framework to do dependency injection. Just instantiate the objects you need and pass them in like normal.

fn main() {
  let mut client = reqwest::Client::new();
  send_requests(&mut client, ...);
}

In this case we know that the mock will only be used in testing and the reqwest client will only be used in production, so you just write your main() and test functions to use them directly.

Later on if you want to swap out implementations at runtime (e.g. using curl instead of reqwest) you can write a factory function which returns a boxed trait object (the equivalent of when you pass around an interface object in Java).

fn create_client() -> Box<dyn HttpClient> {
  if current_day() == "Monday" {
    Box::new(curl::Client::new())
  } else {
    Box::new(reqwest::Client::new())
  }
}

Note that the actual type isn't known until runtime here so you need to use type erasure (trait objects) instead of a compile-time mechanism like impl Trait.

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.