我无法让 Spock 测试正常工作。我想知道如何计算两个不同的模拟交互。
我明白互动需要嘲笑。但是就我而言,我有两个模拟要处理。请参阅下面的示例代码。
我想确保我的
ReportService.makeReport
在AccountService.findUser
抛出异常时被调用一次。
@Getter
@Setter
@Service
// others...
public class AccontService {
@Autowired
private RestTemplate restTemplate;
public Account findUer(Stirng acccountId) throws CustomInvalidExceptionA, CustomInvalidExceptionB {
this.restTemplate.postEntity(/* some params */);
// other logics
throw new CustomInvalidExceptionB("Parent Account does not qualify");
}
}
@Getter
@Setter
@Component
// others...
public class SomeHelper {
@Autowired
private InventoryService inventoryService;
@Autowired
private AccountService accountService;
@Autowired
private ReportSerice reportService;
public ProcessRequest requst(String accountId, Item item) {
try {
Account acct = this.accountService.findUser(accountId);
this.inventoryService.check(item);
} catch (CustomInvalidExceptionA | CustomInvalidExceptionB ex) {
Report report=this.reportService.reportRequest(accountid, item, ex.getClass());
log.error("ERROR" + ex.getClass());
//Other logics using report object
}
}
}
@Getter
@Setter
@Service
// others...
public class ReportService {
public Report reportRequest(String accountId, Item item, String message) {
// some logics to make a report request
return // Report object
}
}
class SomeHelperSpec extends Specification {
SomeHelper helper;
def setUp() {
this.helper = new SomeHelper()
this.helper.accountService = Mock(AccountService)
this.helper.reportService = Mock(ReportService)
}
def "make sure if the accountService findUser failed, then reportService reportRequest is called once"() {
given:
this.helper.accountService.restTemplate = Mock(RestTemplate)
this.helper.accountService.restTemplate.postForEntity(_ as URI, _, String) >> {
throw new CustomInvalidExceptionA()
}
when:
this.helper.accountService.findUser("123")
given:
thrown CustomInvalidExceptionA
1 * this.helper.reportService.reportRequest(*_) >> _
}
}
这里是一组可重现的示例类,在现有代码中有几个修复,以便首先编译和运行您的伪代码示例。顺便说一句,出于这个答案的目的,我用明确的 setter 和 getter 替换了 Lombok:
package de.scrum_master.stackoverflow.q75679629;
public class Account { }
package de.scrum_master.stackoverflow.q75679629;
public class CustomInvalidExceptionA extends Exception { }
package de.scrum_master.stackoverflow.q75679629;
public class CustomInvalidExceptionB extends Exception {
public CustomInvalidExceptionB(String message) {
super(message);
}
}
package de.scrum_master.stackoverflow.q75679629;
public class InventoryService {
public void check(Item item) { }
}
package de.scrum_master.stackoverflow.q75679629;
public class Item { }
package de.scrum_master.stackoverflow.q75679629;
public class ProcessRequest { }
package de.scrum_master.stackoverflow.q75679629;
public class Report { }
package de.scrum_master.stackoverflow.q75679629;
import java.net.URI;
public class RestTemplate {
public void postForEntity(URI uri, Object o, Class<?> clazz) throws CustomInvalidExceptionA { }
}
package de.scrum_master.stackoverflow.q75679629;
public class ReportService {
public Report reportRequest(String accountId, Item item, Class<? extends Exception> message) {
// some logics to make a report request
return new Report();
}
}
package de.scrum_master.stackoverflow.q75679629;
import java.net.URI;
import java.net.URISyntaxException;
public class AccountService {
public RestTemplate getRestTemplate() {
return restTemplate;
}
public void setRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
private RestTemplate restTemplate;
public Account findUser(String acccountId) throws CustomInvalidExceptionA, CustomInvalidExceptionB {
URI uri = null;
try { uri = new URI("https://scrum-master.de"); }
catch (URISyntaxException e) { throw new RuntimeException(e); }
this.restTemplate.postForEntity(uri, "x", Integer.class);
// other logics
throw new CustomInvalidExceptionB("Parent Account does not qualify");
}
}
package de.scrum_master.stackoverflow.q75679629;
public class SomeHelper {
private InventoryService inventoryService;
private AccountService accountService;
private ReportService reportService;
public ProcessRequest request(String accountId, Item item) {
try {
Account acct = this.accountService.findUser(accountId);
this.inventoryService.check(item);
}
catch (CustomInvalidExceptionA | CustomInvalidExceptionB ex) {
Report report = this.reportService.reportRequest(accountId, item, ex.getClass());
System.out.println("ERROR: " + ex.getClass());
//Other logics using report object
}
return new ProcessRequest();
}
public InventoryService getInventoryService() {
return inventoryService;
}
public void setInventoryService(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}
public AccountService getAccountService() {
return accountService;
}
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
public ReportService getReportService() {
return reportService;
}
public void setReportService(ReportService reportService) {
this.reportService = reportService;
}
}
现在我们有一些东西要编译和测试,我们可以这样做:
package de.scrum_master.stackoverflow.q75679629
import spock.lang.Specification
class SomeHelperTest extends Specification {
SomeHelper helper
def setup() {
helper = new SomeHelper()
helper.accountService = new AccountService()
helper.reportService = Mock(ReportService)
helper.inventoryService = Mock(InventoryService)
}
def "make sure if the accountService findUser failed, then reportService reportRequest is called once"() {
given:
helper.accountService.restTemplate = Mock(RestTemplate) {
postForEntity(*_) >> {
throw new CustomInvalidExceptionA()
}
}
when:
helper.request("123", Mock(Item))
then:
noExceptionThrown()
then:
1 * helper.reportService.reportRequest(*_)
}
}
请注意:
AccountService
不应该是 mock,如果你希望它的 findUser
方法正常运行。要么使用普通实例,要么,如果您需要验证其上的交互,则使用 Spy
(此处不需要)。然而,如果你嘲笑它,findUser
将什么都不做,尤其是不调用 restTemplate.postForEntity
。 更新: 请参阅下面的另一个解决方案,您可以在其中模拟此类。
是“given, when, then”,不是“given, when, given”。
thrown CustomInvalidExceptionA
没有任何意义,因为该异常将在SomeHelper.request
中被捕获。
您的 Spock 规范中还有一些其他不正确的细节,我不打算详细说明。
在 Groovy Web 控制台 中尝试。运行规范时,您首先会看到错误日志打印到控制台。要查看实际测试结果,请单击“结果”选项卡:
更新: 如果您想模拟
AccountService
,假设稍后实际使用 Report report
变量,如源代码注释所暗示的那样,另一种甚至可能比以前更好的解决方案是:
public class Report {
public void doSomething() { }
}
public ProcessRequest request(String accountId, Item item) {
try {
Account acct = this.accountService.findUser(accountId);
this.inventoryService.check(item);
}
catch (CustomInvalidExceptionA | CustomInvalidExceptionB ex) {
Report report = this.reportService.reportRequest(accountId, item, ex.getClass());
System.out.println("ERROR: " + ex.getClass());
// Other logic using report object
report.doSomething();
}
return new ProcessRequest();
}
你可以这样测试它,模拟除被测类之外的所有内容:
class SomeHelperTest extends Specification {
SomeHelper helper
def setup() {
helper = new SomeHelper()
helper.accountService = Mock(AccountService)
helper.reportService = Mock(ReportService)
helper.inventoryService = Mock(InventoryService)
}
def "make sure if the accountService findUser failed, then reportService reportRequest is called once"() {
given:
helper.accountService.findUser(_) >> {
throw new CustomInvalidExceptionA()
}
when:
helper.request("123", Mock(Item))
then:
noExceptionThrown()
then:
1 * helper.reportService.reportRequest(*_) >> _
}
}
请注意,在这种情况下,为了使
>> _
方法返回非 reportRequest(*_)
存根响应而不是 null
的模拟默认值,最后一行末尾的 null
确实是必不可少的。简单地使用 Stub(ReportService)
的想法在这里行不通,因为在存根上您无法验证诸如 1 *
之类的交互,即使用模拟并使其返回 null
以外的东西是可行的方法。
在 Groovy Web 控制台中尝试它。