【问题标题】:Mock test not returning any value Java Junit模拟测试不返回任何值Java Junit
【发布时间】:2017-04-21 05:34:23
【问题描述】:

我正在尝试使用 junit 和模拟测试使用休眠连接到数据库的方法。
这是我的代码

UserDAO.java

public interface UserDAO {    
    public void addUser(String username, String password);
    public List<String> getUsers();
}

UserDAOImpl.java

public class UserDAOImpl implements UserDAO {
    public static final Logger LOG = LoggerFactory.getLogger(UserDAOImpl.class);
    private static Session session;

    public UserDAOImpl() {      
    }

   public UserDAOImpl(Session session) {
     this.session = session;
   }
    private static void beginSession() {
        session = DbUtils.getSessionFactory().openSession();
        session.beginTransaction();
    }

    @Override
    public void addUser(String username, String password) {
        String encryptedPassword = Utils.encrypt(password);
        User user = new User(username, encryptedPassword);
        beginSession();
        try {
            session.save(user);
            System.out.println(user.getPassword());
            session.getTransaction().commit();
        } catch (SQLGrammarException e) {
            session.getTransaction().rollback();
            LOG.error("Cannot save user", e);
        } finally {
            session.close();
        }
    }

    @Override
    public List<String> getUsers() {
        beginSession();
        List<String> results = new ArrayList<String>();
        String hql = "select username from User";
        Query query = null;
        try {
            query = session.createQuery(hql);
            results = query.list();
        } catch (HibernateException e) {
            LOG.error("Cannot execute query", e);
        } 
        return results;
    }
}

TestUserDAOImpl

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class TestUserDAOImpl {

    @Mock
    SessionFactory sessionFactory;

    @Mock 
    Session session;

    @Before
    public void setup() {
      when(sessionFactory.getCurrentSession()).thenReturn(session);
    }

    @Test
    public void testCreate() {
        // userDAOImpl = new UserDAOImpl(session);
        UserDAOImpl userDAOImpl = Mockito.mock(UserDAOImpl.class);
        String username = "username";
        String password = "password";
        userDAOImpl.addUser(username, password);
        System.out.println(userDAOImpl.getUsers());
    }
}

测试用例向数据库添加了一组用户名和密码,但是当我尝试使用getUsers() 返回结果时,它返回一个空列表。

谁能帮我解决这个问题?

【问题讨论】:

  • 从你的 addUser 方法永远不会在你的真实对象中被调用的事实开始,因为你使用的是 Mockito 版本。
  • 我该如何解决这个问题?即使在真实对象中调用 addUser 方法,它也不应该破坏主数据库。

标签: java hibernate unit-testing junit mocking


【解决方案1】:

首先,您没有向数据库添加任何用户,因为 userDAOImpl 是一个模拟对象,因此,正如 Joe C 所指出的,addUser 方法永远不会被调用一个真实的对象。出于同样的原因(userDAOImpl 被嘲笑)getUsers 方法不返回任何列表。

正如你告诉sessionFactory 做什么然后它的getCurrentSession() 方法被调用,你可以告诉userDAOImpl 当它的方法addUsergetUsers 被调用时做什么。

附带说明testCreate() 方法不应包含System.out.println 方法,因为JUnit 无法知道您的测试应该通过还是失败。如果您使用Mockito,您可以使用verify 方法来确保某些代码行得到执行。

或者,如果您想测试您的存储库,您可以使用内存数据库并创建真实对象以将数据插入数据库并从中读取数据。这样您的主数据库就不会被测试数据污染。这是内存测试数据库上的一个很好的article


更新使用 Mockito 测试 UserDAOImpl

我做的第一件事是稍微改变了UserDAOImpl 类。原因是:您不能使用Mockito 模拟静态方法(至少在撰写本文时不能)。更多关于这个here

我将session 对象传递给UserDAOImpl 并使用该会话开始事务,而不是使用DbUtils 的静态方法。

这是修改后的UserDAOImpl 类:

package test.mockito;

import java.util.ArrayList;
import java.util.List;

public class UserDAOImpl implements UserDAO {
    public static final Logger LOG = LoggerFactory.getLogger(UserDAOImpl.class);
    private Session session;

    public UserDAOImpl(Session session) {
        this.session = session;
    }

    @Override
    public void addUser(String username, String password) {
        String encryptedPassword = Utils.encrypt(password);
        User user = new User(username, encryptedPassword);
        session.beginTransaction();
        try {
            session.save(user);
            System.out.println(user.getPassword());
            session.getTransaction().commit();
        } catch (SQLGrammarException e) {
            session.getTransaction().rollback();
            if(LOG != null)LOG.error("Cannot save user", e);
        } finally {
            session.close();
        }
    }

    @Override
    public List<String> getUsers() {
        session.beginTransaction();
        List<String> results = new ArrayList<String>();
        String hql = "select username from User";
        Query query = null;
        try {
            query = session.createQuery(hql);
            results = query.list();
        } catch (HibernateException e) {
            if(LOG != null)LOG.error("Cannot execute query", e);
        }
        return results;
    }
}

让我们看看如何使用模拟对象测试UserDAOImpl 的方法。最初,我们模拟我们没有测试的对象,但我们需要它们来执行被测代码的语句。

package test.mockito;

import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class UserDAOImplTest {

    @Mock
    Session session;

    @Mock
    Transaction transaction;

    @Before
    public void setUp() {
        when(session.getTransaction()).thenReturn(transaction);
    }

    @Test
    public void addUserTest() {
        UserDAO userDAO = new UserDAOImpl(session);
        userDAO.addUser("testusername", "testpassword");
        try {
            verify(session).getTransaction();
            verify(session.getTransaction()).commit();
        } catch (SQLGrammarException e) {
            fail(e.getMessage());
        }
        verify(session).close();
    }
}

注意:我们不是在测试Session,我们也不是在测试Transaction;因此,我们使用Mockito 将这些对象提供给我们的UserDAOImpl 类。我们假设方法save(User user)commit()和其他mocked对象的方法工作正常,所以我们只测试addUser方法。使用Mockito,我们不需要与数据库建立真正的会话,而是模拟对象,这更容易和更快(此外,即使我们没有测试addUser 方法也可以还开发了代码的其他部分 - 其他一些开发人员可能正在处理)。

在上面的测试用例中,addUserTest() 方法测试了当模拟对象的方法按预期运行时该方法是否正确执行。通过使用verify(session.getTransacion()).commit(),我们确保调用commit() 方法,否则在catch 块中测试失败。

以同样的方式,我们可以测试addUser 是否在抛出异常时回滚事务。你可以这样做:

@Test
public void addUserTestFails() {
    UserDAO userDAO = new UserDAOImpl(session);
    try {
        doThrow(new SQLGrammarException()).when(session).save(any());
        userDAO.addUser("testusername", "testpassword");
        verify(transaction, never()).commit();
    } catch (SQLGrammarException e) {
        verify(transaction).rollback();
    }
}

此测试用例测试addUser 方法是否在保存更改时抛出异常时表现得异常。当使用这段代码doThrow(new SQLGrammarException()).when(session).save(any()); 调用save 方法时,您可以通过告诉模拟对象抛出异常来模拟异常,然后确保永远不会使用verify(transaction, never()).commit(); 调用commit() 方法。在catch 块中,您验证事务已回滚:verify(transaction).rollback()

这是包含这两个测试用例的完整类:

package test.mockito;

import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class UserDAOImplTest {

    @Mock
    Session session;

    @Mock
    Transaction transaction;

    @Before
    public void setUp() {
        when(session.getTransaction()).thenReturn(transaction);
    }

    @Test
    public void addUserTest() {
        UserDAO userDAO = new UserDAOImpl(session);
        userDAO.addUser("testusername", "testpassword");
        try {
            verify(session).getTransaction();
            verify(session.getTransaction()).commit();
        } catch (SQLGrammarException e) {
            fail(e.getMessage());
        }
        verify(session).close();
    }

    @Test
    public void addUserTestFails() {
        UserDAO userDAO = new UserDAOImpl(session);
        try {
            doThrow(new SQLGrammarException()).when(session).save(any());
            userDAO.addUser("testusername", "testpassword");
            verify(transaction, never()).commit();
        } catch (SQLGrammarException e) {
            verify(transaction).rollback();
        }
    }
}

【讨论】:

  • 如何告诉 userDAOImpl 在调用 addUser 和 getUsers 时该做什么。 when(userDAOImpl.addUser(username, password).thenReturns("") 实际上会抛出编译错误。
  • 你想测试什么?如果你想测试一个特定的方法,那么它不应该被存根。您在真实对象上调用该方法,但仅模拟该方法需要的对象,并且在测试时无法在运行时提供,例如 session 、一些数据库连接等。
  • 如果你需要模拟 void 方法,看看这个link
【解决方案2】:

建议:退后一步。现在。

使用 JUnit 和 Mockito 来测试 hibernate 和 DOA 是没有意义的……当您首先缺乏如何编写单元测试的基本理解时。在您的代码中:

@Test
public void testCreate() {
  // userDAOImpl = new UserDAOImpl(session);
  UserDAOImpl userDAOImpl = Mockito.mock(UserDAOImpl.class);
  String username = "username";
  String password = "password";
  userDAOImpl.addUser(username, password);
  System.out.println(userDAOImpl.getUsers());
}

几乎没有任何意义。典型的单元测试更像是:

class UnderTest {
  Foo foo;
  UnderTest(Foo foo) { this.foo = foo; }

  int bar(String whatever) { return foo.bar(whatever); }
}

class UnderTestTest {
  @Mock
  Foo foo;

  UnderTest underTest;

  @Before
  public void setup() { underTest = new UnderTest(foo); }

  @Test
  public void testBar() {
    when(foo.bar(any()).thenReturn(5);
    assertThat(underTest.bar(), is(5);
  }
}

注意事项:

  • 您不会模拟您打算测试的课程。你模拟你的类被测试需要来完成它的工作的那些对象;但是,您仍然只模拟那些您必须模拟以使测试通过的对象。
  • 然后您在被测对象上调用一个方法;你要么断言一些预期的结果回来了;或者您使用模拟验证来检查对模拟对象的预期调用是否发生。

长话短说:您应该花几天时间阅读有关 JUnit 和 Mockito 的教程。换句话说:在参加跨栏比赛之前先学会爬行!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多