CommandArgumentsMatchers.java
package redis.clients.jedis.util;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.mockito.ArgumentMatcher;
import redis.clients.jedis.CommandArguments;
import redis.clients.jedis.args.Rawable;
import redis.clients.jedis.commands.ProtocolCommand;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
/**
* Utility class providing matchers for CommandArguments testing.
* <p>
* Provides both Mockito ArgumentMatchers (for mock verification) and Hamcrest Matchers (for
* assertions).
* </p>
* <p>
* Hamcrest matcher example usage:
* </p>
*
* <pre>
* {@code
* assertThat(args, hasCommand(Protocol.Command.ZRANGE));
* assertThat(args, hasArgumentCount(3));
* assertThat(args, hasArgument(1, RawableFactory.from(100L)));
* assertThat(args, hasArguments(
* Protocol.Command.ZRANGE,
* RawableFactory.from(0L),
* RawableFactory.from(100L)
* ));
* }
* </pre>
* <p>
* Mockito matcher example usage:
* </p>
*
* <pre>
* {@code
* verify(mock).someMethod(argThat(commandIs(Protocol.Command.GET)));
* verify(mock).someMethod(argThat(commandWithArgs(Protocol.Command.SET, "key")));
* }
* </pre>
*/
public final class CommandArgumentsMatchers {
private CommandArgumentsMatchers() {
throw new InstantiationError("Must not instantiate this class");
}
// ========== Mockito ArgumentMatchers ==========
/**
* Mockito matcher for CommandArguments with specific ProtocolCommand.
* @param command the expected protocol command
* @return an ArgumentMatcher that checks if the CommandArguments has the specified command
*/
public static ArgumentMatcher<CommandArguments> commandIs(ProtocolCommand command) {
return args -> {
if (args == null || !(args instanceof CommandArguments)) {
return false;
}
return command.equals(args.getCommand());
};
}
/**
* Mockito matcher for CommandArguments containing a specific argument (as String).
* @param expectedArg the expected argument value (will be compared as String)
* @return an ArgumentMatcher that checks if the CommandArguments contains the argument
*/
public static ArgumentMatcher<CommandArguments> hasArgument(String expectedArg) {
return args -> {
for (Rawable arg : args) {
if (expectedArg.equals(SafeEncoder.encode(arg.getRaw()))) {
return true;
}
}
return false;
};
}
/**
* Mockito matcher for CommandArguments with specific command and containing a specific argument.
* @param command the expected protocol command
* @param expectedArg the expected argument value (will be compared as String)
* @return an ArgumentMatcher that checks both command and argument
*/
public static ArgumentMatcher<CommandArguments> commandWithArgs(ProtocolCommand command,
String expectedArg) {
return cmd -> commandIs(command).matches(cmd) && hasArgument(expectedArg).matches(cmd);
}
// ========== Hamcrest Matchers ==========
/**
* Matches CommandArguments with a specific command.
* @param expectedCommand the expected protocol command
* @return a matcher that checks if the CommandArguments has the specified command
*/
public static Matcher<CommandArguments> hasCommand(ProtocolCommand expectedCommand) {
return new TypeSafeMatcher<CommandArguments>() {
@Override
protected boolean matchesSafely(CommandArguments args) {
return expectedCommand.equals(args.getCommand());
}
@Override
public void describeTo(Description description) {
description.appendText("CommandArguments with command ").appendValue(expectedCommand);
}
@Override
protected void describeMismatchSafely(CommandArguments args,
Description mismatchDescription) {
mismatchDescription.appendText("was CommandArguments with command ")
.appendValue(args.getCommand());
}
};
}
/**
* Matches CommandArguments with a specific argument count.
* @param expectedSize the expected number of arguments (including the command)
* @return a matcher that checks if the CommandArguments has the specified size
*/
public static Matcher<CommandArguments> hasArgumentCount(int expectedSize) {
return new TypeSafeMatcher<CommandArguments>() {
@Override
protected boolean matchesSafely(CommandArguments args) {
return args.size() == expectedSize;
}
@Override
public void describeTo(Description description) {
description.appendText("CommandArguments with argument count ").appendValue(expectedSize);
}
@Override
protected void describeMismatchSafely(CommandArguments args,
Description mismatchDescription) {
mismatchDescription.appendText("was CommandArguments with argument count ")
.appendValue(args.size());
}
};
}
/**
* Matches CommandArguments with a specific argument at a specific index.
* @param index the index of the argument (0-based, where 0 is the command)
* @param expectedArg the expected Rawable argument
* @return a matcher that checks if the CommandArguments has the specified argument at the index
*/
public static Matcher<CommandArguments> hasArgument(int index, Rawable expectedArg) {
return new TypeSafeMatcher<CommandArguments>() {
@Override
protected boolean matchesSafely(CommandArguments args) {
if (index < 0 || index >= args.size()) {
return false;
}
return expectedArg.equals(args.get(index));
}
@Override
public void describeTo(Description description) {
description.appendText("CommandArguments with argument at index ").appendValue(index)
.appendText(" equal to ").appendValue(expectedArg);
}
@Override
protected void describeMismatchSafely(CommandArguments args,
Description mismatchDescription) {
if (index < 0 || index >= args.size()) {
mismatchDescription.appendText("index ").appendValue(index)
.appendText(" is out of bounds (size: ").appendValue(args.size()).appendText(")");
} else {
mismatchDescription.appendText("argument at index ").appendValue(index)
.appendText(" was ").appendValue(args.get(index));
}
}
};
}
/**
* Matches CommandArguments with a specific sequence of arguments.
* <p>
* The first argument should be the command, followed by the parameters.
* </p>
* @param expectedArgs the expected sequence of Rawable arguments (command + parameters)
* @return a matcher that checks if the CommandArguments matches the sequence
*/
public static Matcher<CommandArguments> hasArguments(Rawable... expectedArgs) {
return new TypeSafeMatcher<CommandArguments>() {
@Override
protected boolean matchesSafely(CommandArguments args) {
if (args.size() != expectedArgs.length) {
return false;
}
Iterator<Rawable> iter = args.iterator();
for (Rawable expected : expectedArgs) {
if (!iter.hasNext() || !expected.equals(iter.next())) {
return false;
}
}
return true;
}
@Override
public void describeTo(Description description) {
List<String> decodedExpectedArgs = Arrays.stream(expectedArgs).map(Rawable::getRaw)
.map(SafeEncoder::encode).collect(Collectors.toList());
description.appendText("CommandArguments with arguments ").appendValue(decodedExpectedArgs);
}
@Override
protected void describeMismatchSafely(CommandArguments args,
Description mismatchDescription) {
List<Rawable> actualArgs = new ArrayList<>();
args.forEach(actualArgs::add);
List<String> decodedActualArgs = actualArgs.stream().map(Rawable::getRaw)
.map(SafeEncoder::encode).collect(Collectors.toList());
mismatchDescription.appendText("was CommandArguments with arguments ")
.appendValue(decodedActualArgs);
}
};
}
}