Reputation: 149
Hello I'm testing the class that has some validating methods and I've been wondering if there is a way to reduce the duplicated code.
@Test
void testCorrectEmailValidator() {
List<String> correctEmails = Arrays.asList("[email protected]", "[email protected]", "[email protected]",
"[email protected]", "[email protected]", "[email protected]");
for (String email : correctEmails) {
boolean isValid = UserCredentialsValidator.emailValidator(email);
System.out.println("Email is valid: " + email + ": " + isValid);
assertTrue(isValid);
}
}
@Test
void testCorrectUsernameValidator() {
List<String> correctUsernames = Arrays.asList("username", "123username", "username3", "user2name",
"USERNAME", "USERNAME123", "123USERNAME123", "2uSERname33");
for(String username : correctUsernames) {
boolean isValid = UserCredentialsValidator.usernameValidation(username, userList);
System.out.println("Username is valid: " + username + " : " + isValid);
assertTrue(isValid);
}
}
I also have validators for other fields such as username etc. I was thinking about implementing a helper method that would accept: tested credential as String, List but I've got a problem with last parameter - a validating method, not sure how to pass that.
The code i would like to replace with some method is the for loop.
Upvotes: 6
Views: 527
Reputation: 3807
Fact that you're struggling to test is indicating a design smell.
Its good time for you to explore strategy design pattern here.
Basically you main code would look something like
interface IValidator {
boolean isValid(List<String> yourDataToBeValidated);
}
Now create multiple validator classes for different fields like email, username etc.
class EmailValidator implements IValidator {
boolean isValid(List<String> yourDataToBeValidated){
//Email specific validations goes here
}
}
You can create more validators as you need on the go.
Now in your unit tests create new EmailValidator()
or new UsernameValidator()
and pass your emailIds
or usernames
to be isValid()
method, something like below :
boolean isValid = new EmailValidator().isValid(Arrays.asList("[email protected]", "[email protected]");
assertTrue(isValid);
Upvotes: 0
Reputation: 49606
I am afraid your tests are of low quality.
The problems that should be fixed immediately include
UserCredentialsValidator.usernameValidation(username, userList);
The method shouldn't take the second argument. The place from where that list is retrieved should be concealed from the API consumer.
List<String> correctEmails = Arrays.asList(...)
and List<String> correctUsernames = Arrays.asList(...)
should be removed. You'd better make the tests parameterised with @ParameterizedTest
and @ValueSource
.
I'd rather remove the System.out.println
statements. They make little sense in tests.
@ParameterizedTest
@ValueSource(strings = {"[email protected]", "[email protected]"})
void testUserEmailValidationWithValidUserEmailShouldPass(String validUserEmail) {
boolean isValid = UserCredentialsValidator.emailValidator(validUserEmail);
assertTrue(isValid);
}
@ParameterizedTest
@ValueSource(strings = {"username", "123username"})
void testUserNameValidationWithValidUserNameShouldPass(String validUserName) {
boolean isValid = UserCredentialsValidator.usernameValidation(validUserName);
assertTrue(isValid);
}
Now there is nothing to reduce.
Upvotes: 4
Reputation: 12000
Use parameterized tests:
static Stream<String> emailsSource() {
return Stream.of("[email protected]", "[email protected]", "[email protected]",
"[email protected]", "[email protected]", "[email protected]");
}
@Test
@MethodSource("emailsSource")
void testCorrectEmailValidator(String email) {
boolean isValid = UserCredentialsValidator.emailValidator(email);
assertTrue(isValid);
}
Repeat for usernameSource etc. IMHO, this is sufficient to eliminate duplicities.
However if you want to go further and generalize it, use method references. I wouldn't recommend it though.
static Stream<Pair<String,Predicate<String>>> allSources() {
return Stream.of(
Pair.of("[email protected]", UserCredentialsValidator::emailValidator),
Pair.of("username", UserCredentialsValidator::usernameValidationOneArg), // like usernameValidation but with argument userList fixed
...
);
}
@Test
@MethodSource("allSources")
void testAll(Pair<String,Predicate<String>> p) {
String s = p.getLeft();
Predicate<String> test = p.getRight();
boolean isValid = test.apply(email);
assertTrue(isValid);
}
Upvotes: 0
Reputation: 88707
As I already stated in my comment to your question, I'm not sure rearranging your code would help much. However, as a comparision here's a Java8+ version which uses a common method:
@Test
void testCorrectEmailValidator() {
List<String> correctEmails = Arrays.asList("[email protected]", "[email protected]", "[email protected]",
"[email protected]", "[email protected]", "[email protected]");
testValidator( "Email", correctEmails , email -> UserCredentialsValidator.emailValidator(email) );
}
@Test
void testCorrectUsernameValidator() {
List<String> correctUsernames = Arrays.asList("username", "123username", "username3", "user2name",
"USERNAME", "USERNAME123", "123USERNAME123", "2uSERname33");
//I don't know where userList does come from but it would need to be final to be used here
testValidator( "Username", correctUsernames, username -> UserCredentialsValidator.usernameValidation(username, userList) );
}
void testValidator( String name, List<String> data, Predicate<String> validator) {
for( String element : data ) {
boolean isValid = validator.test( element );
System.out.println( name + " is valid: " + element + " : " + isValid);
assertTrue(isValid);
}
}
In that particular case both approaches would be 23 lines long while the second one might be easier to reuse but harder to understand and less flexible (e.g. if you'd need to pass additional parameters etc.)
Upvotes: 0