javamat
javamat

Reputation: 149

passing static method as parameter in Java

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

Answers (4)

Shanu Gupta
Shanu Gupta

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

Andrew
Andrew

Reputation: 49606

I am afraid your tests are of low quality.

The problems that should be fixed immediately include

  1. 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.

  2. 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.

  3. 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

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

Thomas
Thomas

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

Related Questions