Reputation: 1189
What would be the most idiomatic way to write tests for a CLI program using clap
? I'm currently doing it like this:
#[derive(Debug, Parser)]
#[clap(author, version, about)]
pub struct StructArgs {
#[clap(subcommand)]
pub command_type: CommandType,
}
#[derive(Debug, Subcommand)]
pub enum CommandType {
Command1(Command1Args),
...
}
#[derive(Debug, Args)]
pub struct Command1Args {
pub field: String,
...
}
impl Command1Args {
...
}
#[test]
fn test_do_stuff() {
let args = StructArgs::try_parse_from(
std::iter::once("<PROGRAM NAME>")
.chain(
["<ARG 1>", ..., "<ARG n>"]
.iter()
.cloned()
)
);
if let CommandType::Command1(command1_args) = args.command_type {
// do stuff with command1_args
} else {
panic!();
}
}
Basically I pass to clap
an iterator of arguments, then I check if the parsed command structure matches with the CommandType
I expect, and I proceed to test its methods and internal state. The panic
in the else branch is for failing the test if for some reason I get an unexpected CommandType
, meaning most likely that I have written something wrong in the iterator.
Can this be improved further?
Upvotes: 8
Views: 3863
Reputation: 2572
Chayim certainly has a point when he says you should not test Clap. But there are scenarios where this is not the case and writing unit tests for a CLI parser implemented with clap does make sense.
In my case, I want to define a group of arguments that is only required if a certain flag is set, and if set, all arguments of that group are required. This is not a trivial thing to define in Clap, and it makes sense testing it, for example like this (using Clap 4.4):
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn oidc_all_or_nothing() {
/* Call it without any OIDC args, just graqphiql; must work */
if let Err(err) = AppConfig::try_parse_from(vec!["aisrv", "--enable-graphiql"].iter()) {
panic!("Just --enable-graphiql failed: {err}");
}
/* Call with only one OIDC arg, must fail */
if AppConfig::try_parse_from(vec!["aisrv", "--oidc-idp-url", "tescht"].iter()).is_ok() {
panic!("Just one OIDC arg did not fail!");
}
/* Call with all OIDC args, must succeed */
if let Err(err) = AppConfig::try_parse_from(
vec![
"aisrv",
"--oidc-idp-url",
"tescht",
"--oidc-super-admin-user",
"tescht",
"--oidc-super-admin-password",
"tescht",
"--oidc-super-admin-client-id",
"tescht",
"--oidc-super-admin-client-secret",
"tescht",
"--oidc-aud",
"tescht",
]
.iter(),
) {
panic!("All OIDC args passed, but still fails: {err}");
}
}
}
Upvotes: 4