简体   繁体   English

如何编写一个junit来验证是否捕获了该方法引发的异常?

[英]How to write a junit to verify if an exception thrown by the method is caught?

I have below piece of code in my spring boot app, which validates email addresses 我的春季启动应用程序中包含以下代码,该代码可验证电子邮件地址

class EmailValidation {

    public static void validate(List<String> s){
        try {
            for (String address : s) {
                if (s == null || s.indexOf("@") < 0) {  
                    throw new InvalidEmailAddressException("Email address is invalid ");
                }
                new InternetAddress(s);
            }
        } catch(AddressException e){
            LOGGER.Error("Please validate email addresses");
        }
        catch(InvalidEmailAddressesException e){
            LOGGER.error(e.getMessage());
        }
    }

    class InvalidEmailAddressException extends Exception {

        public InvalidEmailAddressException(String message) {
            super(message)
        }
    }
}

I want to write a Junit test which will verify that that InvalidEmailAddressesException was thrown and CAUGHT. 我想编写一个Junit测试,它将验证是否抛出InvalidEmailAddressesException并执行了CAUGHT。 How can I do that in JUnit? 如何在JUnit中做到这一点?

In general I agree with the comments that such a test is probably unnecessary. 总的来说,我同意这样的评论,即这种测试可能是不必要的。

However, if I wanted to test something like that I would test the two cases separately and that requires a small modification to your code. 但是,如果我想测试类似的东西,我将分别测试这两种情况,这需要对您的代码进行少量修改。

Firstly I would construct a method that only throws the exception if there is one. 首先,我将构造一个仅在存在异常时抛出异常的方法。

public static void checkAddresses(List<String> s) throws AddressException, InvalidEmailAddressException {
    for (String address : s) {
         if (s == null || s.indexOf("@") < 0) {  
             throw new InvalidEmailAddressException("Email address is invalid ");
         }
         new InternetAddress(s);
    }
}

then I would use it in your code like that: 那么我会像这样在您的代码中使用它:

class EmailValidation {

    public static void validate(List<String> s){
         try {
             checkAddresses(s); // a wrapper method that throws the expected exceptions
         } catch(AddressException e){
             LOGGER.Error("Please validate email addresses");
         }
         catch(InvalidEmailAddressesException e){
             LOGGER.error(e.getMessage());
         }
     }

     // add checkAddresses here or somewhere appropriately

     class InvalidEmailAddressException extends Exception {

         public InvalidEmailAddressException(String message) {
             super(message)
         }
     }

} }

Then, I would write separate tests for checkAddresses that tests both if an exception is expected or not and separate tests for validate , (possibly with the same input that was given to checkAddresses ) that should pass if an exception isn't thrown. 然后,我将为checkAddresses编写单独的测试,以测试是否预期发生异常,以及为validate编写单独的测试(可能具有与checkAddresses相同的输入),如果未引发异常,则该测试应通过。

Also, if you would like to verify your logs may be you could try something like that . 另外,如果您想验证你的日志可能是你可以尝试像那个

Indeed using java Exception for common cause is considered a bad practice, and as @Michael said, Exceptions must be exceptional , because 实际上,将java Exception用于常见原因被认为是一种不好的做法,正如@Michael所说, Exceptions必须是例外 ,因为

  • they break flow control 他们破坏了流量控制
  • they are slow (more details here How slow are Java exceptions? ) 它们很慢(这里更多详细信息,Java异常有多慢?
  • they do not mix with functional paradigm (where Java is in part going to with the addition of lamda-expressions 它们不与功能范式混合使用(Java在某种程度上与lamda表达式有关)

However, creating a custom object for wrapping validation data is a good thing and InvalidEmailAddressException can be turned into CheckedEmail : 然而,对于包装验证数据创建一个自定义对象是一件好事, InvalidEmailAddressException可以变成CheckedEmail

import java.util.List;
import java.util.stream.Collectors;

public class EmailValidator {

    public List<CheckedEmail> validate(List<String> emailAddresses) {
        return emailAddresses.stream().map(this::validate).collect(Collectors.toList());
    }

    public CheckedEmail validate(String emailAddress) {
        String[] emailParts = emailAddress.toString().split( "@", 3 );
        final boolean valid;
        if ( emailParts.length != 2 ) {
            valid = false;
        } else {
            // More validation can go here using one or more regex
            valid = true;
        }
        return new CheckedEmail(emailAddress, valid);
    }

    public static final class CheckedEmail {
        private final String emailAddress;
        private final boolean valid;

        private CheckedEmail(String emailAddress, boolean valid) {
            this.emailAddress = emailAddress;
            this.valid = valid;
        }

        public String getEmailAddress() {
            return emailAddress;
        }

        public boolean isValid() {
            return valid;
        }
    }
}

This in turn can be tested quite easily (and improved with a parameterized test): 这反过来可以很容易地进行测试(并通过参数化测试进行了改进):

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Arrays;
import java.util.List;

import org.junit.Test;

public class EmailValidatorTest {

    private final EmailValidator emailValidator = new EmailValidator();

    @Test
    public void invalid_email() {
        EmailValidator.CheckedEmail checkedEmail = emailValidator.validate("missing.an.at.symbol");

        assertThat(checkedEmail.isValid()).isFalse();
    }

    @Test
    public void valid_email() {
        EmailValidator.CheckedEmail checkedEmail = emailValidator.validate("at.symbol@present");

        assertThat(checkedEmail.isValid()).isTrue();
    }

    @Test
    public void multiple_email_addresses() {
        List<String> emailAddresses = Arrays.asList("missing.an.at.symbol", "at.symbol@present");

        List<EmailValidator.CheckedEmail> checkedEmails = emailValidator.validate(emailAddresses);

        assertThat(checkedEmails)
                .extracting(ce -> ce.getEmailAddress() + " " + ce.isValid())
                .containsExactly(
                        "missing.an.at.symbol false",
                        "at.symbol@present true");
    }
}

If somewhere the point is just to log this, then: 如果某个地方只是记录此内容,则:

List<EmailValidator.CheckedEmail> checkedEmails = emailValidator.validate(emailAddresses);

checkedEmails.stream()
        .filter(ce -> !ce.isValid())
        .map(ce -> String.format("Email address [%s] is invalid", ce.getEmailAddress()))
        .forEach(logger::error);

Hope this helps ! 希望这可以帮助 !

Don't approach testing that way. 不要以这种方式进行测试。 You should test only the specified behaviour of your code, not its implementation details. 您应该仅测试代码的指定行为,而不是其实现详细信息。

If the method you are testing delegates to a method that throws a checked exception, and the method you are testing does not also declare that it throws that checked exception, the compiler will enforce that the method catches the exception. 如果您正在测试的方法委托给引发已检查异常的方法,并且您正在测试的方法未同时声明其throws已检查异常,则编译器将强制该方法捕获该异常。 So in that case a unit test is unnecessary. 因此,在这种情况下,不需要进行单元测试。

If the method you are testing delegates to a method that throws an unchecked exception, consult the specification of the method to determine whether it is acceptable for the method under test to also throw (propagate) that exception. 如果您正在测试的方法委托引发未检查异常的方法,请查阅该方法的规范,以确定被测方法也抛出(传播)该异常是否可以接受。 If it is not acceptable for it to propagate the exception, then you should create a test case that causes the the method delegated to to throw that unchecked exception. 如果它不能传播异常,则应创建一个测试用例,以使委托的方法引发该未经检查的异常。 If the method propagates the exception, the test case will fail. 如果该方法传播异常,则测试用例将失败。 How to do that? 怎么做? That depends on the method being delegated to, but in most cases you will need to use Dependency Injection to supply a mock object that throws the exception. 这取决于委托给的方法,但是在大多数情况下,您将需要使用“依赖注入”来提供引发异常的模拟对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM