[英]Groovy Spock - mocked method not returning desired value
到目前为止,我有一个带有单一方法的简单 Java 类,它从数据库中获取数据然后进行一些更新,它看起来像这样:
@Slf4j
@Service
public class PaymentServiceImpl implements PaymentService {
private final PaymentsMapper paymentsMapper;
private final MyProperties myProperties;
public PaymentServiceImpl(PaymentsMapper paymentsMapper,
MyProperties myProperties) {
this.paymentsMapper = paymentsMapper;
this.myProperties = myProperties;
}
@Override
@Transactional
public void doSomething() {
List<String> ids = paymentsMapper.getPaymentIds(
myProperties.getPayments().getOperator(),
myProperties.getPayments().getPeriod().getDuration().getSeconds());
long updated = 0;
for (String id : ids ) {
updated += paymentsMapper.updatedPaymentsWithId(id);
}
}
}
为了记录, MyProperties 类是一个@ConfigurationProperties
类,它从application.properties
获取属性,它看起来像这样:
@Data
@Configuration("myProperties")
@ConfigurationProperties(prefix = "my")
@PropertySource("classpath:application.properties")
public class MyProperties {
private Payments payments;
@Getter
@Setter
public static class Payments {
private String operator;
private Period period;
@Getter @Setter
public static class Period{
private Duration duration;
}
}
}
现在我正在尝试为这种方法编写一个简单的测试,我想出了这个:
class PaymentServiceImplTest extends Specification {
@Shared
PaymentsMapper paymentsMapper = Mock(PaymentsMapper)
@Shared
MyProperties properties = new MyProperties()
@Shared
PaymentServiceImpl paymentService = new PaymentServiceImpl(paymentsMapper, properties)
def setupSpec() {
properties.setPayments(new MyProperties.Payments())
properties.getPayments().setOperator('OP1')
properties.getPayments().setPeriod(new MyProperties.Payments.Period())
properties.getPayments().getPeriod().setDuration(Duration.ofSeconds(3600))
}
def 'update pending acceptation payment ids'() {
given:
paymentsMapper.getPaymentIds(_ as String, _ as long) >> Arrays.asList('1', '2', '3')
when:
paymentService.doSomething()
then:
3 * paymentsMapper.updatedPaymentsWithId(_ as String)
}
}
但试图运行测试我得到一个空指针异常:
java.lang.NullPointerException
at com.example.PaymentServiceImpl.doSomething(PaymentServiceImpl.java:33)
at com.example.service.PaymentServiceImplTest.update pending acceptation payment ids(PaymentServiceImplTest.groovy:33)
有人能告诉我这是为什么吗? 为什么我在那里得到 NPE?
我对 Spock 的 pom.xml 依赖如下:
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<scope>test</scope>
</dependency>
你在这里有几个问题:
您使用@Shared
变量。 您应该只在绝对必要时才这样做,例如,如果您需要实例化像 DB 连接这样的昂贵对象。 否则,存在来自特性方法 A 的上下文渗入 B 的危险,因为共享对象已被修改。 然后功能突然变得对执行顺序敏感,这是它们不应该的。
您的模拟也是共享的,但您正在尝试从功能方法中指定存根结果和交互。 如果您在多个特征方法中这样做,这将不会像您预期的那样工作。 在这种情况下,您应该为每个功能创建一个新实例,这也意味着共享变量不再有意义。 唯一可能有意义的情况是使用完全相同的模拟实例而不对所有功能方法进行任何更改。 但是,像3 * mock.doSomething()
这样的交互将继续跨功能计数。 此外,模拟总是便宜的,那么为什么首先要共享模拟呢?
交互paymentsMapper.getPaymentIds(_ as String, _ as long)
在您的情况下不匹配,因此默认返回值为null
。 原因是 Groovy 中第二个参数的运行时类型是Long
。 因此,您需要将参数列表更改为(_ as String, _ as Long)
或更简单的(_ as String, _)
(_, _)
, (*_)
,具体取决于您的匹配需要的具体程度。
因此,您可以执行以下任一操作:
不要对您的字段使用@Shared
并将setupSpec
重命名为setup
。 非常简单,规范不会因此而明显变慢。
如果您坚持使用共享变量,请确保在setupSpec
方法中或内联在模拟定义中仅设置一次两个模拟交互,即类似
@Shared PaymentsMapper paymentsMapper = Mock(PaymentsMapper) { getPaymentIds(_ as String, _ as Long) >> Arrays.asList('1', '2', '3') 3 * updatedPaymentsWithId(_ as String) } // ... def 'update pending acceptation payment ids'() { expect: paymentService.doSomething() }
但是模拟交互在特征方法之外,这可能会让读者想知道特征方法实际上是做什么的。 因此,从技术上讲,这是可行的,但易于阅读的测试看起来与 IMO 不同。
您可能还想在每个功能方法中实例化和配置共享模拟。 但是,对@Shared PaymentServiceImpl paymentService
的一次性分配将使用另一个实例或null
。 哦哦! 你看到共享模拟的问题了吗? 我想,不值得你过早的优化,因为这就是我所相信的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.