在调用 void ServiceImplementationLayer 方法的 JUnit 测试用例上抛出 Mockito UnfinishedStubbingException
Mockito UnfinishedStubbingException thrown on a JUnit test case which calls a void ServiceImplementationLayer method
我正在 Maven 项目上使用 Mockito/JUnit,用于基于控制台的应用程序,采用 DAO 设计模式。我的 IDE 是 Spring Tools Suite 3.
问题是,每次我在我的特定 JUnit 测试中 运行 这个特定测试时,我都会得到一个 UnfinishedStubbingException
,我不知道为什么会这样,因为语法看起来是正确的。我对单元测试和 Mockito 非常陌生,但我认为这是因为从一层到下一层的抽象级别由于某种原因混淆了 Mockito。因此,我最初尝试在测试用例中的服务对象上使用 Spy 而不是 Mock (但 NotAMockException
是而是被抛出)。
任何建议 and/or 如何解决此问题的建议将不胜感激。
这是堆栈跟踪:
org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at com.revature.testing.BankAccountEvaluationService.testMakeDeposit_ValidUserId(BankAccountEvaluationService.java:91)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
Hints:
1. missing thenReturn()
2. you are trying to stub a final method, which is not supported
3: you are stubbing the behaviour of another mock inside before 'thenReturn' instruction if completed
at com.revature.testing.BankAccountEvaluationService.testMakeDeposit_ValidUserId(BankAccountEvaluationService.java:91)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=10=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
示例代码如下:
BankAccountEvaluationTest
class:
@InjectMocks
private AccountServiceImpl service;
@Mock
private AccountDaoImpl daoMock;
@Before
public void setUp() {
service = Mockito.spy(new AccountServiceImpl());
MockitoAnnotations.initMocks(this);
}
@Test
public void testMakeDeposit_ValidUserId() {
//setup
Account account = Mockito.mock(Account.class);
account.setAccountId(1);
double amount = 20.50;
//gives UnfinishedStubbingException -> Mockito doesn't like this because it's mocking within a mocking object
//doNothing().when(daoMock).updateAccountBalance(account.getBalance() + amount, accountNumber); //Solution?
//run
service.makeDeposit(amount, account.getAccountId());
//verify
verify(service, times(1)).makeDeposit(amount, account.getAccountId());
}
AccountServiceImpl
class:
package com.revature.serviceimpl;
import java.util.List;
import org.apache.log4j.Logger;
import com.revature.dao.AccountDao;
import com.revature.daoimpl.AccountDaoImpl;
import com.revature.model.Account;
import com.revature.service.AccountService;
public class AccountServiceImpl implements AccountService {
private static Logger logger = Logger.getLogger(AccountServiceImpl.class);
private AccountDao accountDao = new AccountDaoImpl();
//other overridden methods from AccountService interface
@Override
public void makeDeposit(double addedCash, int id) {
logger.info("Sending deposit request to the database.");
// find the account
Account account = accountDao.selectAccountByAccountId(id);
System.out.println(account);
// set new balance
account.setBalance(account.getBalance() + addedCash);
double myNewBalance = account.getBalance();
logger.info("New balance: " + account.getBalance());
logger.info("Updating account balance to account number " + id);
// update the database
accountDao.updateAccountBalance(myNewBalance, id);
}
}
AccountDaoImpl
class:
package com.revature.daoimpl;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import com.revature.dao.AccountDao;
import com.revature.model.Account;
import com.revature.model.AccountStatus;
import com.revature.model.AccountType;
public class AccountDaoImpl implements AccountDao {
private static Logger logger = Logger.getLogger(UserDaoImpl.class);
private static String url = MY_URL;
private static String dbUsername = MY_DATABASE_NAME;
private static String dbPassword = MY_DATABASE_PASSWORD;
//other overridden methods from AccountDao interface
@Override
public void updateAccountBalance(double balance, int id) {
try (Connection conn = DriverManager.getConnection(url, dbUsername, dbPassword)) {
String sql = "UPDATE accounts SET account_balance = ? WHERE account_id = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setDouble(1, balance);
ps.setInt(2, id);
ps.executeUpdate();
logger.info("new balance is now set");
} catch (SQLException e) {
logger.warn("Error in SQL execution to update balance. Stack Trace: ", e);
}
}
}
自从我上次使用 Mockito 以来已经有一段时间了,但它可以代替
doNothing().when(daoMock).updateAccountBalance(account.getBalance() + amount, accountNumber);
你应该使用
doNothing().when(daoMock).updateAccountBalance(Mockito.any(), Mockito.any());
...也许你也可以试试
when(daoMock.updateAccountBalance(Mockito.any(), Mockito.any())).doNothing();
我认为(但我不确定)您只是在用参数 (account.getBalance() + amount, accountNumber)
模拟确切的调用
例如如果您在设置模拟时的帐号是 5,那么您只是在模拟 accountnumber = 5
的单个呼叫
这里的模拟似乎有些混乱。
使用您拥有的代码,您可能希望为 AccountServiceImpl
编写测试,但模拟 AccountDao
。您不使用与数据库对话的真实 DAO,而是使用模拟 DAO,您可以在调用某些方法时将其设置为 return 某些值。您还可以查询模拟以了解它被调用了多少次以及调用了哪些值。
模拟域对象
在您的测试中,您似乎还选择了模拟 Account
class。您没有在问题中包含 Account
class 但我猜它只有 getter 和 setter。如果是这样,我不会费心去嘲笑它。与其使用模拟账户,不如使用真实账户。替换行
Account account = Mockito.mock(Account.class);
与
Account account = new Account();
这样您就可以在 account
上调用 getter 和 setter,它们的行为将如您所愿。如果你坚持使用模拟账户,你将不得不使用 Mockito 来实现 getters 和 setters:
when(account.getId()).thenReturn(...)
when(account.getBalance()).thenReturn(...)
// ...
您还通过您的模拟账户致电 account.setId()
。这不会执行任何操作,因为当您在模拟帐户上调用 .setId()
时,您还没有告诉 Mockito 要做什么。在真实帐户上调用 account.setId()
将设置 ID。
除了被测试的以外,您不必模拟每个 class。你是否这样做取决于其他 classes 做什么。如果另一个 class 有一些复杂的逻辑,或者与数据库、文件系统、网络等通信,那么模拟它会很有用,这样你的测试就不必担心复杂的逻辑或与外部系统的通信。然而,在这种情况下,我们不必费心去嘲笑 Account
,因为它不会做任何复杂的事情。
模拟设置
创建模拟时,所有 void
方法将不执行任何操作,所有其他方法将 return false
、零或 null
视情况而定。如果你想让他们做别的事情,你需要设置他们。
您的服务使用您的 DAO 从数据库加载帐户。给定 ID 后,您需要将 mock accountDao
设置为 return account
。通过在调用 service.makeDeposit(...)
:
的行之前将以下行添加到测试中来执行此操作
when(daoMock.selectAccountByAccountId(1)).thenReturn(account);
但是 updateAccountBalance()
方法呢?默认情况下,它什么都不做,你似乎试图将它设置为什么都不做,它已经这样做了。您可以删除尝试设置此方法的行,因为它什么也做不了。稍后,我们将查看验证此方法,即断言它已被调用。
模拟中的模拟
您收到此行的错误:
doNothing().when(daoMock).updateAccountBalance(account.getBalance() + amount, accountNumber)
设置一个模拟时,您不能在另一个模拟上调用方法。你为什么需要?如果它是一个 mock,那么您应该已经将 mock 方法设置为 return 某个值,因此只需使用该值即可。换句话说,而不是写
// if account is a mock...
when(account.getBalance()).thenReturn(10.00);
doNothing().when(daoMock).updateAccountBalance(account.getBalance() + amount, accountNumber)
随便写
// if account is a mock...
when(account.getBalance()).thenReturn(10.00);
doNothing().when(daoMock).updateAccountBalance(10.00 + amount, accountNumber)
在这一行中,您正在尝试设置 daoMock
并正在调用 account.getBalance()
。如果 account
也是 mock,这将导致问题。
导致问题的原因是 Mockito 的工作方式。 Mockito 看不到你的源代码,它看到的只是对其自身静态方法的调用和对 mock 的调用。行
doNothing().when(daoMock).updateAccountBalance(account.getBalance() + amount, accountNumber)
导致以下交互序列:
- 已调用静态 Mockito 方法
doNothing()
,
when()
任何对象 doNothing()
调用的方法,
account.getBalance()
方法调用,
updateAccountBalance()
调用了模拟 DAO 的方法。 (在计算完所有参数之前,我们不能调用方法。)
对于 Mockito,前三个步骤与以下没有区别:
doNothing().when(daoMock);
when(account.getBalance()).thenReturn(...);
在这种情况下,很明显我们还没有完成设置daoMock
。我们希望在这种情况下出现异常。
Mockito 的语法使模拟变得清晰和富有表现力,但如果不小心,您有时可能会遇到这样的情况。
我们已经决定删除导致此问题的行,因此本节更多的是提供信息和理解。
验证
接下来,我们看下面几行:
//run
service.makeDeposit(amount, account.getAccountId());
//verify
verify(service, times(1)).makeDeposit(amount, account.getAccountId());
这是做什么的?您调用 makeDeposit
方法,然后 您验证您调用了 makeDeposit
方法 。这个验证真的不用了,上面三行就可以清楚的看到这个方法被调用了
通常,您不会验证正在测试的 class 上的方法。相反,您在 class 调用的模拟上验证方法。您要做的是验证是否使用预期值调用了模拟 DAO 上的相关方法:
verify(daoMock, times(1)).updateAccountBalance(account.getBalance() + amount, account.getAccountId());
您也可以取消对 Mockito.spy(...)
的调用。我自己从来没有使用过间谍,而且我认为这里不需要它们。替换行
service = Mockito.spy(new AccountServiceImpl());
与
service = new AccountServiceImpl();
总之
这里有相当多的内容,希望其中至少有一部分是有意义的,可以让您更好地理解正在发生的事情。我对您的测试进行了上述更改并通过了测试。
我正在 Maven 项目上使用 Mockito/JUnit,用于基于控制台的应用程序,采用 DAO 设计模式。我的 IDE 是 Spring Tools Suite 3.
问题是,每次我在我的特定 JUnit 测试中 运行 这个特定测试时,我都会得到一个 UnfinishedStubbingException
,我不知道为什么会这样,因为语法看起来是正确的。我对单元测试和 Mockito 非常陌生,但我认为这是因为从一层到下一层的抽象级别由于某种原因混淆了 Mockito。因此,我最初尝试在测试用例中的服务对象上使用 Spy 而不是 Mock (但 NotAMockException
是而是被抛出)。
任何建议 and/or 如何解决此问题的建议将不胜感激。
这是堆栈跟踪:
org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at com.revature.testing.BankAccountEvaluationService.testMakeDeposit_ValidUserId(BankAccountEvaluationService.java:91)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
Hints:
1. missing thenReturn()
2. you are trying to stub a final method, which is not supported
3: you are stubbing the behaviour of another mock inside before 'thenReturn' instruction if completed
at com.revature.testing.BankAccountEvaluationService.testMakeDeposit_ValidUserId(BankAccountEvaluationService.java:91)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=10=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
示例代码如下:
BankAccountEvaluationTest
class:
@InjectMocks
private AccountServiceImpl service;
@Mock
private AccountDaoImpl daoMock;
@Before
public void setUp() {
service = Mockito.spy(new AccountServiceImpl());
MockitoAnnotations.initMocks(this);
}
@Test
public void testMakeDeposit_ValidUserId() {
//setup
Account account = Mockito.mock(Account.class);
account.setAccountId(1);
double amount = 20.50;
//gives UnfinishedStubbingException -> Mockito doesn't like this because it's mocking within a mocking object
//doNothing().when(daoMock).updateAccountBalance(account.getBalance() + amount, accountNumber); //Solution?
//run
service.makeDeposit(amount, account.getAccountId());
//verify
verify(service, times(1)).makeDeposit(amount, account.getAccountId());
}
AccountServiceImpl
class:
package com.revature.serviceimpl;
import java.util.List;
import org.apache.log4j.Logger;
import com.revature.dao.AccountDao;
import com.revature.daoimpl.AccountDaoImpl;
import com.revature.model.Account;
import com.revature.service.AccountService;
public class AccountServiceImpl implements AccountService {
private static Logger logger = Logger.getLogger(AccountServiceImpl.class);
private AccountDao accountDao = new AccountDaoImpl();
//other overridden methods from AccountService interface
@Override
public void makeDeposit(double addedCash, int id) {
logger.info("Sending deposit request to the database.");
// find the account
Account account = accountDao.selectAccountByAccountId(id);
System.out.println(account);
// set new balance
account.setBalance(account.getBalance() + addedCash);
double myNewBalance = account.getBalance();
logger.info("New balance: " + account.getBalance());
logger.info("Updating account balance to account number " + id);
// update the database
accountDao.updateAccountBalance(myNewBalance, id);
}
}
AccountDaoImpl
class:
package com.revature.daoimpl;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import com.revature.dao.AccountDao;
import com.revature.model.Account;
import com.revature.model.AccountStatus;
import com.revature.model.AccountType;
public class AccountDaoImpl implements AccountDao {
private static Logger logger = Logger.getLogger(UserDaoImpl.class);
private static String url = MY_URL;
private static String dbUsername = MY_DATABASE_NAME;
private static String dbPassword = MY_DATABASE_PASSWORD;
//other overridden methods from AccountDao interface
@Override
public void updateAccountBalance(double balance, int id) {
try (Connection conn = DriverManager.getConnection(url, dbUsername, dbPassword)) {
String sql = "UPDATE accounts SET account_balance = ? WHERE account_id = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setDouble(1, balance);
ps.setInt(2, id);
ps.executeUpdate();
logger.info("new balance is now set");
} catch (SQLException e) {
logger.warn("Error in SQL execution to update balance. Stack Trace: ", e);
}
}
}
自从我上次使用 Mockito 以来已经有一段时间了,但它可以代替
doNothing().when(daoMock).updateAccountBalance(account.getBalance() + amount, accountNumber);
你应该使用
doNothing().when(daoMock).updateAccountBalance(Mockito.any(), Mockito.any());
...也许你也可以试试
when(daoMock.updateAccountBalance(Mockito.any(), Mockito.any())).doNothing();
我认为(但我不确定)您只是在用参数 (account.getBalance() + amount, accountNumber)
模拟确切的调用例如如果您在设置模拟时的帐号是 5,那么您只是在模拟 accountnumber = 5
的单个呼叫这里的模拟似乎有些混乱。
使用您拥有的代码,您可能希望为 AccountServiceImpl
编写测试,但模拟 AccountDao
。您不使用与数据库对话的真实 DAO,而是使用模拟 DAO,您可以在调用某些方法时将其设置为 return 某些值。您还可以查询模拟以了解它被调用了多少次以及调用了哪些值。
模拟域对象
在您的测试中,您似乎还选择了模拟 Account
class。您没有在问题中包含 Account
class 但我猜它只有 getter 和 setter。如果是这样,我不会费心去嘲笑它。与其使用模拟账户,不如使用真实账户。替换行
Account account = Mockito.mock(Account.class);
与
Account account = new Account();
这样您就可以在 account
上调用 getter 和 setter,它们的行为将如您所愿。如果你坚持使用模拟账户,你将不得不使用 Mockito 来实现 getters 和 setters:
when(account.getId()).thenReturn(...)
when(account.getBalance()).thenReturn(...)
// ...
您还通过您的模拟账户致电 account.setId()
。这不会执行任何操作,因为当您在模拟帐户上调用 .setId()
时,您还没有告诉 Mockito 要做什么。在真实帐户上调用 account.setId()
将设置 ID。
除了被测试的以外,您不必模拟每个 class。你是否这样做取决于其他 classes 做什么。如果另一个 class 有一些复杂的逻辑,或者与数据库、文件系统、网络等通信,那么模拟它会很有用,这样你的测试就不必担心复杂的逻辑或与外部系统的通信。然而,在这种情况下,我们不必费心去嘲笑 Account
,因为它不会做任何复杂的事情。
模拟设置
创建模拟时,所有 void
方法将不执行任何操作,所有其他方法将 return false
、零或 null
视情况而定。如果你想让他们做别的事情,你需要设置他们。
您的服务使用您的 DAO 从数据库加载帐户。给定 ID 后,您需要将 mock accountDao
设置为 return account
。通过在调用 service.makeDeposit(...)
:
when(daoMock.selectAccountByAccountId(1)).thenReturn(account);
但是 updateAccountBalance()
方法呢?默认情况下,它什么都不做,你似乎试图将它设置为什么都不做,它已经这样做了。您可以删除尝试设置此方法的行,因为它什么也做不了。稍后,我们将查看验证此方法,即断言它已被调用。
模拟中的模拟
您收到此行的错误:
doNothing().when(daoMock).updateAccountBalance(account.getBalance() + amount, accountNumber)
设置一个模拟时,您不能在另一个模拟上调用方法。你为什么需要?如果它是一个 mock,那么您应该已经将 mock 方法设置为 return 某个值,因此只需使用该值即可。换句话说,而不是写
// if account is a mock...
when(account.getBalance()).thenReturn(10.00);
doNothing().when(daoMock).updateAccountBalance(account.getBalance() + amount, accountNumber)
随便写
// if account is a mock...
when(account.getBalance()).thenReturn(10.00);
doNothing().when(daoMock).updateAccountBalance(10.00 + amount, accountNumber)
在这一行中,您正在尝试设置 daoMock
并正在调用 account.getBalance()
。如果 account
也是 mock,这将导致问题。
导致问题的原因是 Mockito 的工作方式。 Mockito 看不到你的源代码,它看到的只是对其自身静态方法的调用和对 mock 的调用。行
doNothing().when(daoMock).updateAccountBalance(account.getBalance() + amount, accountNumber)
导致以下交互序列:
- 已调用静态 Mockito 方法
doNothing()
, when()
任何对象doNothing()
调用的方法,account.getBalance()
方法调用,updateAccountBalance()
调用了模拟 DAO 的方法。 (在计算完所有参数之前,我们不能调用方法。)
对于 Mockito,前三个步骤与以下没有区别:
doNothing().when(daoMock);
when(account.getBalance()).thenReturn(...);
在这种情况下,很明显我们还没有完成设置daoMock
。我们希望在这种情况下出现异常。
Mockito 的语法使模拟变得清晰和富有表现力,但如果不小心,您有时可能会遇到这样的情况。
我们已经决定删除导致此问题的行,因此本节更多的是提供信息和理解。
验证
接下来,我们看下面几行:
//run
service.makeDeposit(amount, account.getAccountId());
//verify
verify(service, times(1)).makeDeposit(amount, account.getAccountId());
这是做什么的?您调用 makeDeposit
方法,然后 您验证您调用了 makeDeposit
方法 。这个验证真的不用了,上面三行就可以清楚的看到这个方法被调用了
通常,您不会验证正在测试的 class 上的方法。相反,您在 class 调用的模拟上验证方法。您要做的是验证是否使用预期值调用了模拟 DAO 上的相关方法:
verify(daoMock, times(1)).updateAccountBalance(account.getBalance() + amount, account.getAccountId());
您也可以取消对 Mockito.spy(...)
的调用。我自己从来没有使用过间谍,而且我认为这里不需要它们。替换行
service = Mockito.spy(new AccountServiceImpl());
与
service = new AccountServiceImpl();
总之
这里有相当多的内容,希望其中至少有一部分是有意义的,可以让您更好地理解正在发生的事情。我对您的测试进行了上述更改并通过了测试。