Reputation: 2708
I am trying to test a method that connects to db using hibernate using junit and mocking.
Here is my code
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());
}
}
The test case adds a set of username and password to the db but when I try to return the results using getUsers()
it returns a null list.
Can anyone please help me fix this?
Upvotes: 1
Views: 2406
Reputation: 1938
First off, you are not adding any users to the DB, because userDAOImpl
is a mocked object, therefore, as Joe C has pointed out, the addUser
method never gets called on a real object. For the same reason (userDAOImpl
is mocked) the getUsers
method doesn't return any list.
Just as you have told sessionFactory
what to do then its getCurrentSession()
method is called, you can tell userDAOImpl
what to do when its methods addUser
and getUsers
get called.
As a side note: the testCreate()
method should not contain a System.out.println
method, because JUnit
cannot know whether your test should pass or fail. If you are using Mockito
, you can use the verify
method to make sure certain lines of code are getting executed.
Alternatively, if you want to test your repository, you can use an in-memory database and create real objects to insert data into and read from the database. This way your main database doesn't get polluted with test data. Here's a good article on in-memory test databases.
UPDATE: Testing the UserDAOImpl
class using Mockito
The first thing I did is, changed the UserDAOImpl
class a bit. The reason being: you can't mock static methods using Mockito
(at least not at the moment of writing this post). More on this here.
I'm passing the session
object to UserDAOImpl
and using that session to begin the transaction, instead of using the static method of DbUtils
.
Here's the modified UserDAOImpl
class:
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;
}
}
Let's see how you can test the methods of UserDAOImpl
using mocked objects. Initially, we mock objects that we are not testing, but we need them in order to execute statements of the code under test.
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();
}
}
Note: we are not testing Session
, neither are we testing Transaction
; therefore we are providing these objects to our UserDAOImpl
class using Mockito
. We assume that methods save(User user)
, commit()
, and other methods of mocked objects, are working properly, so we only test the addUser
method. Using Mockito
we don't need to establish a real session to the database, but mock the object instead, which is much easier and faster (moreover, it makes it possible to test the addUser
method even if we haven't developed yet the other parts of the code - some other developer may be working on that).
In the test case above, the addUserTest()
method, tests if this method executes properly when the methods of mocked objects behave as they are expected to. By using verify(session.getTransacion()).commit()
we make sure that the commit()
method is called, otherwise in the catch
block the test fails.
In the same way we can test if addUser
rolls the transaction back when an exception is thrown. Here's how you can do it:
@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();
}
}
This test case, tests the addUser
method if it behaves as excepted when there's an exception thrown while saving the changes. You simulate the exception by telling the mocked object to throw an exception when the save
method is called using this piece of code doThrow(new SQLGrammarException()).when(session).save(any());
, and then you make sure that the commit()
method is never called using verify(transaction, never()).commit();
. In the catch
block you verify that the transaction is rolled back: verify(transaction).rollback()
.
Here's the complete class that contains these two test cases:
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();
}
}
}
Upvotes: 2
Reputation: 140457
Suggestion: step back. Now.
There is no point in using JUnit and Mockito for testing hibernate and DOAs ... when you lack the basic understanding how to write unit tests in the first place. In your code:
@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());
}
almost nothing makes sense. A typical unit test goes more like:
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);
}
}
Notes:
Long story short: you should rather spent a few days reading tutorials about JUnit and Mockito. In other words: learn to crawl before going for the hurdle race!
Upvotes: 1