首页 > 解决方案 > 在调用 void ServiceImplementationLayer 方法的 JUnit 测试用例上抛出 Mockito UnfinishedStubbingException

问题描述

我在一个 Maven 项目中使用 Mockito/JUnit 来实现一个基于控制台的应用程序,它具有 DAO 设计模式。我的 IDE 是 Spring Tools Suite 3。

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

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

这是堆栈跟踪:

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

标签: javaspringmavenmockitojunit4

解决方案


自从我上次使用 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 的呼叫


推荐阅读