微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在JUnit测试用例上抛出Mockito UnfinishedStubbingException,该用例调用了无效的ServiceImplementationLayer方法 模拟域对象模拟设置在模拟中进行模拟验证总结

如何解决在JUnit测试用例上抛出Mockito UnfinishedStubbingException,该用例调用了无效的ServiceImplementationLayer方法 模拟域对象模拟设置在模拟中进行模拟验证总结

我在Maven项目上使用Mockito / JUnit,用于具有DAO设计模式的基于控制台的应用程序。我的IDE是Spring Toools Suite 3。

问题是,每次在特定的JUnit测试上运行此特定测试时,我都会得到一个UnfinishedStubbingException,但我不知道为什么这样做,因为语法看起来正确。我对单元测试和Mockito还是很陌生,但是我认为这是发生的,因为从一层到下一层的抽象级别由于某种原因使Mockito感到困惑。因此,在测试用例中,我最初尝试在服务对象上使用 Spy Mock (但随后会抛出NotAMockException)。 / p>

对于解决此问题的任何建议和/或建议,将不胜感激。

这是堆栈跟踪:

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$1.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$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runchildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.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类:

@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类:

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类:

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,那么您只是在模拟一个帐号为5的电话

,

在这里进行模拟似乎有些混乱。

有了代码,您将希望为AccountServiceImpl编写一个测试,但要模拟AccountDao。您可以使用模拟DAO来设置它,以在调用某些方法时返回某些值,而不是使用与数据库对话的真实DAO。您还可以查询该模拟,以找出该模拟被调用了多少次以及具有什么值。

模拟域对象

在测试中,您似乎还选择了模拟Account类。您没有在问题中加入Account类,但我想它只是包含getter和setter。如果是这样,我不会打扰它。与其使用模拟帐户,不如使用真实帐户。替换行

        Account account = Mockito.mock(Account.class);

使用

        Account account = new Account();

这样,您可以在account上调用getter和setter,它们的行为将与您期望的一样。如果您坚持使用模拟帐户,则必须使用Mockito来实现getter和setter:

        when(account.getId()).thenReturn(...)        
        when(account.getBalance()).thenReturn(...)
        // ...

您还可以在模拟帐户上致电account.setId()。这将无济于事,因为在模拟帐户上致电.setId()时,您没有告诉Mockito该怎么做。在真实帐户上致电account.setId()会设置ID。

除了要测试的课程外,您不必模拟其他所有课程。是否执行取决于这些其他类的功能。如果另一个类具有某种复杂的逻辑,或者与数据库,文件系统,网络等进行通信,则可以将其模拟出来,这样您的测试就不必担心复杂的逻辑或与外部的通信。系统。但是,在这种情况下,我们不要打扰Account,因为它不会做任何复杂的事情。

模拟设置

创建模拟时,所有void方法将不执行任何操作,而所有其他方法将视情况返回false,零或null。如果要他们做其他事情,则需要设置它们。

您的服务使用您的DAO从数据库中加载帐户。您需要将模拟accountDao设置为在给定ID后返回account。为此,请在调用service.makeDeposit(...)的行之前在测试中添加以下行:

        when(daoMock.selectAccountByAccountId(1)).thenReturn(account);

但是updateAccountBalance()方法呢?默认情况下,它什么都不做,您似乎正在尝试将其设置为不做任何事,而这已经做了。您可以删除尝试设置此方法的行,因为它将无法实现任何操作。稍后,我们将研究验证此方法,即断言它已被调用。

在模拟中进行模拟

此行出现错误:

        doNothing().when(daoMock).updateAccountBalance(account.getBalance() + amount,accountNumber)

设置一个模拟时,不能在另一个模拟上调用方法。为什么要这样?如果是模拟,那么您应该已经设置了模拟方法以返回某个值,因此只需使用该值即可。换句话说,不是写作

        // 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也是模拟的,则将导致问题。

引起问题的原因是由于Mockito的工作方式。 Mockito看不到您的源代码,它看到的只是对它自己的静态方法的调用和对模拟的调用。线

        doNothing().when(daoMock).updateAccountBalance(account.getBalance() + amount,accountNumber)

导致以下互动序列:

  1. 调用了静态Mockito方法doNothing()
  2. 调用对象when()
  3. doNothing()方法,
  4. account.getBalance()方法被调用,
  5. 模拟的DAO的
  6. updateAccountBalance()方法。 (在评估完所有参数之前,我们无法调用方法。)

对于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方法。确实不需要进行验证:您可以清楚地看到此方法在上面称为三行。

通常,您不会在要测试的类上验证方法。相反,您可以验证类调用的模拟中的方法。相反,您要做的是验证模拟DAO上的相关方法是否已调用了预期值:

        verify(daoMock,times(1)).updateAccountBalance(account.getBalance() + amount,account.getAccountId());

您也可以摆脱对Mockito.spy(...)的呼叫。我自己从未使用过间谍,在这里也看不到需要间谍。替换行

        service = Mockito.spy(new AccountServiceImpl());

使用

        service = new AccountServiceImpl();

总结

这里有很多,希望至少有一部分是有意义的,并且可以使您更好地了解正在发生的事情。我对您的测试进行了上述更改,并使其通过。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。