如何编写一个junit来验证方法抛出的异常是否被捕获



我在春季启动应用程序中有下面的一段代码,它可以验证电子邮件地址

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)
}
}
}

我想写一个Junit测试,它将验证InvalidEmailAddressesException是否被抛出并被捕获。我怎么能在JUnit中做到这一点?

总的来说,我同意这样的评论,即这样的测试可能是不必要的。

然而,如果我想测试这样的东西,我会分别测试这两个案例,这需要对代码进行小的修改。

首先,我将构造一个方法,该方法只在存在异常的情况下抛出异常。

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);
}
}

那么我会在你的代码中这样使用:

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)
}
}

}

然后,我将为checkAddresses编写单独的测试,测试是否出现异常,并为validate编写单独的检测(可能具有与checkAddresses相同的输入(,如果没有引发异常,则应通过该检测。

此外,如果你想验证你的日志可能是,你可以尝试这样的东西。

事实上,出于共同原因使用java异常被认为是一种糟糕的做法,正如@Michael所说,异常必须是异常,因为

  • 他们破坏了流量控制
  • 它们很慢(此处提供更多详细信息Java异常的速度有多慢?(
  • 它们不与函数范式混合(Java在一定程度上要添加lamda表达式

但是,创建一个用于包装验证数据的自定义对象是一件好事,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;
}
}
}

这反过来可以很容易地进行测试(并通过参数化测试进行改进(:

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");
}
}

如果某个地方的重点只是记录这一点,那么:

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);

希望这能有所帮助!

不要以这种方式进行测试。您应该只测试代码的指定行为,而不是它的实现细节。

如果正在测试的方法委托给抛出已检查异常的方法,并且正在测试的该方法没有同时声明为已检查异常的throws,则编译器将强制该方法捕获该异常。因此,在这种情况下,单元测试是不必要的。

如果要测试的方法委托给抛出未检查异常的方法,请参阅该方法的规范,以确定被测试的方法是否可以同时抛出(传播(该异常。如果它传播异常是不可接受的,那么您应该创建一个测试用例,使委托给的方法抛出未检查的异常。如果该方法传播异常,则测试用例将失败。如何做到这一点?这取决于被委托的方法,但在大多数情况下,您需要使用依赖注入来提供抛出异常的模拟对象。

最新更新