I got some unit test that stores some Person data into the database. To do this i use the EntityManagerFactory.
#Test
public void findPersonById() {
personDao.savePerson(new Person("Hans"));
Person person = new Person("Peter");
personDao.savePerson(person);
Long id = person.getId();
flushAndClear();
person = personDao.findById(id);
assertThat(person.getName(), is("Peter"));
}
where
private EntityManager entityManager;
public Person savePerson(Person person) {
entityManager.persist(person);
return person;
}
and
public Person findById(Long id) {
Person person = entityManager.find(Person.class, id);
return person;
}
with this Base-Test-class
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import org.junit.After;
import org.junit.Before;
public class AbstractDaoTest {
protected EntityManager entityManager = Persistence.createEntityManagerFactory("DefaultPersistenceUnit")
.createEntityManager();
protected PersonDao personDao = new PersonDao();
#Before
public void setUp() {
personDao.setEntityManager(entityManager);
entityManager.getTransaction().begin();
}
#After
public void tearDown() {
entityManager.getTransaction().rollback();
}
protected void flushAndClear() {
entityManager.flush();
entityManager.clear();
}
}
My unit test runs green but when i debug this test, i can't see any data on the db (using squirrel). It doesn't matter in which step the debugger currently is, the Tables are always empty. I am using the following configuration for squirrel:
jdbc:h2:file:~/test;DB_CLOSE_DELAY=-1;LOCK_MODE=0;AUTO_SERVER=TRUE
with Driver: H2
Can you help with this issue?
This has happened because your test cases are not properly designed.
Firstly, If you wish the data to be added to the database, you should commit the changes after any update/insert operation. You have to do a rollback only if there are any exceptions during transactions.
Design your tests some thing like this.
#BeforeTest
public void setUp() {
personDao.setEntityManager(entityManager);
entityManager.getTransaction().begin();
}
#AfterTest
public void tearDown() {
// TODO clear all inserted data either using sql statement or JPA
//Rollback only if there is any exception
entityManager.getTransaction().rollback();
}
#Test
public void findPersonById() {
Long id = System.currentTimeMillis();
personDao.savePerson(new Person(id,"Hans"));
entityManager.flush();
// TODO your assert statements go in here
/* Data is visible till here during your debug.
Once AfterTest is executed then the your data would be rolled back
*/
}
I have used testNG as my Unit testing tool, pls use appropriate tool based on your requirements.
Also, if you are enityManager.flush() insert/update statements would be sent to the DB but wont be committed until commit instruction is sent to the DB by HIbernate. Pls refer this answer for further details
Related
I created a SpringBoot test:
#RunWith(SpringRunner.class)
#SpringBootTest
#TestPropertySource(locations = "classpath:application-dev.properties")
#Transactional
public class ContactTests2 {
private Logger log = LogManager.getLogger();
#PersistenceContext
private EntityManager entityManager;
#Autowired
private ContactRepository customerRepository;
#Autowired
private StoreRepository storeRepository;
#Autowired
private NoteService noteService;
#Autowired
private Validator validator;
private Store store;
#Before
#WithMockUser(roles = "ADMIN")
public void setup() {
log.debug("Stores {}", storeRepository.count());
store = createStore();
storeRepository.save(store);
}
#Test
#WithMockUser(roles = "ADMIN")
public void saveWithNote() {
Contact customer = new Contact();
customer.setPersonType(PersonType.NATURAL_PERSON);
customer.setFirstName("Daniele");
customer.setLastName("Rossi");
customer.setGender(Gender.MALE);
customer.setBillingCountry(Locale.ITALY.getCountry());
customer.setShippingCountry(Locale.ITALY.getCountry());
customer.setStore(store);
Note note = new Note();
note.setGenre(NoteGenre.GENERIC);
note.setOperationType(AuditType.NOTE);
note.setText("note");
customer = customerRepository.save(customer);
noteService.addNote(note, customer);
}
#Test
#WithMockUser(roles = "ADMIN")
public void save() {
Contact customer = new Contact();
customer.setPersonType(PersonType.NATURAL_PERSON);
customer.setFirstName("Daniele");
customer.setLastName("Rossi");
customer.setGender(Gender.MALE);
customer.setBillingCountry(Locale.ITALY.getCountry());
customer.setShippingCountry(Locale.ITALY.getCountry());
customer.setStore(store);
customerRepository.save(customer);
assertEquals(customer, customerRepository.findById(customer.getId()).get());
}
// ====================================================
//
// UTILITY METHODS
//
// ====================================================
private Store createStore() {
Store store = new Store();
store.setName("Padova");
store.setCode("PD");
store.setCountry("IT");
return store;
}
}
this is the note service:
#Service
#Transactional
#PreAuthorize("isAuthenticated()")
public class NoteService {
#PersistenceContext
private EntityManager entityManager;
#Autowired
private NoteRepository noteRepository;
/**
* Add a note to a specific object (parent).
*
* #param note
* #param parent
* #return the added note
*/
public Note addNote(Note note, Persistable<Long> parent) {
// ****************************************************
// VALIDATION CHECKS
// ****************************************************
Assert.notNull(note, InternalException.class, ExceptionCode.INTERNAL_ERROR);
Assert.notNull(parent, InternalException.class, ExceptionCode.INTERNAL_ERROR);
// ****************************************************
// END VALIDATION CHECKS
// ****************************************************
note.setParentId(parent.getId());
note.setParentType(parent.getClass().getSimpleName());
note.setRemoteAddress(NetworkUtils.getRemoteIpFromCurrentContext());
note = noteRepository.save(note);
return note;
}
}
I'm using Hibernate and Mysql 5.7. The problem is that the test called saveWithNote(). When I run this test, following tests fails because the setup() method throw a duplicated exception. It seems the previous test is not rolledback.
This is what happens:
Removing the line noteService.addNote(note, customer); everything works like a charm.
What am I doing wrong? Why test isolation is not preserved?
This is because you are using a real data store as the dependency.
When running saveWithNote(), the customer entry is persisted in database. It is not removed in your test setup, so the when you run save(), you bump into a duplicate database entry.
Solution 1:
Use teardown() method to remove database entries you created during the test.
Example:
#After
#WithMockUser(roles = "ADMIN")
public void teardown() {
// delete the customer entry here
}
Reference: https://examples.javacodegeeks.com/core-java/junit/junit-setup-teardown-example/
Solution 2: Every time you run setup(), wipe the database tables clean.
Example:
#Before
#WithMockUser(roles = "ADMIN")
public void setup() {
// wipe your database tables to make them empty
}
Both solution 1 and 2 should be done with test database only. You DON'T want to clean up production DB.
Solution 3 (recommended):
Use mocked repositories and mock injection (instead of autowiring repositories with real implementation).
Sample/ Reference: https://stackoverflow.com/a/36004293/5849844
Most likely your table is using MyISAM storage engine which does not support transactions (as per Table 15.2 MyISAM Storage Engine Features docs).
Redefine the table using InnoDB storage engine. Take a look at 14.8.1.1 Creating InnoDB Tables docs, it should be on by default but you can check it with:
SELECT ##default_storage_engine;
I'm attempting to update an Entity called User and although the changes are made to the user object successfully, when I attempt to save the user (via the update method) to the database it does not persist. Other functions of this class work such as get(). No exceptions appear.
-----------------------------AbstractDAOImpl.class-----------------------------------
#Transactional
#Repository
public abstract class AbstractDaoImpl<T> {
private static final Logger LOG = Logger.getLogger(StockController.class);
private Class currentClass;
#PersistenceContext(type = PersistenceContextType.EXTENDED)
protected EntityManager entityManager;
protected void setThisClass(Class currentClass) {
this.currentClass = currentClass;
}
#SuppressWarnings("unchecked")
public T get(String id) {
return (T) entityManager.find(currentClass, id);
}
public void delete(String id) {
entityManager.remove(get(id));
}
public void update(T t) {
entityManager.merge(t);
entityManager.flush();
}
#SuppressWarnings("unchecked")
public List list(String tableName) {
return entityManager.createQuery("from " + tableName, currentClass).getResultList();
}
}
------------------------Application.properties------------------------
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.username=SYSTEM
spring.datasource.password=password
spring.datasource.url=jdbc:oracle:thin:#localhost:1521:XE
spring.jpa.database-platform=Oracle11gDialect
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming.strategy=org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle8iDialect
spring.resources.static-locations=file:src/main/resources/
spring.resources.cache-period=0
spring.thymeleaf.cache=false
spring.http.converters.preferred-json-mapper=jackson
security.basic.enabled=false
security.headers.content-type=true
security.enable-csrf=true
security.basic.path=/**
I have made various attempts to implement other fixes listed on here with no luck, including getting the transaction from the entity manager beginning and ending it with the current contents of update() in between. If other information is needed such as the entity classes please let me know and I'll edit my post.
The issue wasn't that my User object wasn't being persisted, it was that the List of another entity inside it called holdings wasn't. In order to fix this I added cascade=CascadeType.ALL to the list of one to many annotations attribute like so.
#OneToMany(mappedBy = "user", cascade= CascadeType.ALL)
private List<UserHolding> holdings;
I have a class, Student and the generated Endpoint class for it. ListStudents and insertStudents methods work without any problems, but update and remove don't cause any change in the datastore. The methods don't throw any errors and the call returns, but no changes are made.
My endpoints code is mostly the code generated by google plugin for eclipse:
#ApiMethod(name = "removeStudent", path="remove_student")
public void removeStudent(#Named("email") String email) {
EntityManager mgr = getEntityManager();
try {
Student student = getStudentByEmailName(email);
mgr.remove(student);
} finally {
mgr.close();
}
}
Entitiy manager getter method:
private static EntityManager getEntityManager() {
return EMF.get().createEntityManager();
}
#ApiMethod(name = "updateStudent")
public Student updateStudent(Student student) {
EntityManager mgr = getEntityManager();
try {
if (!containsStudent(student)) {
throw new EntityNotFoundException("Object does not exist");
}
mgr.persist(student);
} finally {
mgr.close();
}
return student;
}
And my EMF class:
public final class EMF {
private static final EntityManagerFactory emfInstance = Persistence
.createEntityManagerFactory("transactions-optional");
private EMF() {
}
public static EntityManagerFactory get() {
return emfInstance;
}
}
The client that uses this endpoint is Android. I have only tried testing on my local server.
Please tell me if I'm doing something wrong. Thank you
Do you have your student entities indexed by email?
This is a typical issue when you move to nosql and expect all queries to work without indexes.
Note that records inserted before defining index would not be in index.
The datastore is eventually consistent and your code should work. What is the return value that you get in the Student object from your updateStudent method.
As much as I don't want to, after you do a mgr.persist(...) , add mgr.flush() and see if that makes a difference.
Is there a best-practice pattern for completely resetting a database to a freshly-paved schema with JPA before a unit test? I have been using a testing persistence unit with hbml2ddl.auto=create-or-drop and recreating EMFs before each test, but I wonder if there's a cleaner way to do it.
Unit tests should not talk to the database.
Assuming you're writing an integration test for your data access layer, you could use a tool like DBUnit, or you could create a static test helper that programmatically resets your database state by doing all of your deletes and inserts using JPA queries inside of a transaction.
Resetting the database is not a big problem if you use a fast Java database such as the H2 database or HSQLDB. Compared to using Oracle / MySQL (or whatever you use for production) this will speed up your tests, and it will ensure all your code is tested as when using the 'real' production database.
For maximum performance, you can use H2 in-memory (that way you may not have to reset the database manually - it's reset automatically if the connection is closed), or you can use a regular persistent database. To reset the database after use in H2, run the (native) statement 'drop all objects delete files'.
DBUnit has much of what you need, I use Springs Testing framework to rollback, transactions after each test see http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/testing.html
Is there a best-practice pattern for completely resetting a database to a freshly-paved schema with JPA before a unit test?
Don't reset the whole DB schema before every unit test, but reset the "DB environment (which is specific to the current unit test)" at END of each unit test.
We have an entity...
#Entity
public class Candidate implements {
private String id;
private String userName;
private EntityLifeCycle lifeCycle;
protected Candidate() {
}
public Candidate(String userName) {
this.userName = userName;
}
#Id #GeneratedValue(generator="uuid", strategy=GenerationType.AUTO)
#GenericGenerator(name="uuid", strategy="uuid", parameters = {})
#Column(name="candidate_id", nullable=false, unique=true)
public String getId() {
return id;
}
protected void setId(String id) {
this.id = id;
}
#Column(name="user_name", nullable=false, unique=true)
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
#Embedded
public EntityLifeCycle getLifeCycle() {
if(lifeCycle == null) {
lifeCycle = new EntityLifeCycleImpl();
}
return lifeCycle;
}
public void setLifeCycle(EntityLifeCycleImpl lifeCycle) {
this.lifeCycle = lifeCycle;
}
#PrePersist
public void prePersist() {
lifeCycle.setCreatedDate(new Date());
}
}
We are setting the createdDate for each Candidate instance in prePersist() method. Here is a test case that asserts that createdDate is getting set properly....
public class EntityLifeCycleTest {
#Test
public void testLifeCycle() {
EntityManager manager = entityManagerFactory.createEntityManager();
Candidate bond = new Candidate("Miss. Bond");
EntityTransaction tx = manager.getTransaction();
tx.begin();
manager.persist(bond);
tx.commit();
Assert.assertNotNull(bond.getLifeCycle().getCreatedDate());
manager.close();
}
}
This test case will run properly for the first time. But if we run this test case second time it would throw ConstraintViolationException, because the userName is unique key.
Therefore, I think the right approach is to "clean the DB environment (which is specific to the current unit test)" at end of each test case. Like this...
public class EntityLifeCycleTest extends JavaPersistenceTest {
#Test
public void testLifeCycle() {
EntityManager manager = entityManagerFactory.createEntityManager();
Candidate bond = new Candidate("Miss. Bond");
EntityTransaction tx = manager.getTransaction();
tx.begin();
manager.persist(bond);
tx.commit();
Assert.assertNotNull(bond.getLifeCycle().getCreatedDate());
/* delete Candidate bond, so next time we can run this test case successfully*/
tx = manager.getTransaction();
tx.begin();
manager.remove(bond);
tx.commit();
manager.close();
}
}
I have been using a testing persistence unit with hbml2ddl.auto=create-or-drop and recreating EMFs before each test, but I wonder if there's a cleaner way to do it.
Recreating EMF before each test is time consuming, IMO.
Drop and recreate the DB schema only if you have made some changes to #Entity annotated class that affects the underlying DB (e.g. adding/removing columns and/or constraints). So first validate the schema, if the schema is valid don't recreate it, and if invalid then recreate it. Like this...
public class JavaPersistenceTest {
protected static EntityManagerFactory entityManagerFactory;
#BeforeClass
public static void setUp() throws Exception {
if(entityManagerFactory == null) {
Map<String, String> properties = new HashMap<String, String>(1);
try {
properties.put("hibernate.hbm2ddl.auto", "validate");
entityManagerFactory = Persistence.createEntityManagerFactory("default", properties);
} catch (PersistenceException e) {
e.printStackTrace();
properties.put("hibernate.hbm2ddl.auto", "create");
entityManagerFactory = Persistence.createEntityManagerFactory("default", properties);
}
}
}
}
Now, if you run all the test cases(that extend JavaPersistenceTest) in one go, the EMF will be created only once(or twice if the schema was invalid).
I am doing tests on an ejb3-project using ejb3unit session bean test. The following test will fail with the last assertNotSame() check.
public void testSave() {
Entity myEntity = new Entity();
myEntity.setName("name1");
myEntity = getBeanToTest().save(myEntity);
assertNotSame("id should be set", 0l, myEntity.getId());
// now the problem itself ...
int count = getBeanToTest().findAll().size();
assertNotSame("should find at least 1 entity", 0, count);
}
So, what is happening. The save(entity) method delivers my "persisted" object with an id set. But when I'll try to find the object using findAll() it won't deliver a single result. How can I get my ServiceBean.save method to work, so the persisted entity can be found?
Edit
My ServiceBean looks like this
#Stateless
#Local(IMyServiceBean.class)
public class MyServiceBean implements IMyServiceBean {
#PersistenceContext(unitName = "appDataBase")
private EntityManager em;
public Entity save(Entity entity) {
em.merge(entity);
}
public List<Entity> findAll() {
... uses Query to find all Entities ..
}
}
and for ejb3unit the ejb3unit.properties:
ejb3unit_jndi.1.isSessionBean=false
ejb3unit_jndi.1.jndiName=project/MyServiceBean/local
ejb3unit_jndi.1.className=de.prj.MyServiceBean
Here we go..
public void testSave() {
Entity myEntity = .. // create some valid Instance
// ...
EntityTransaction tx = this.getEntityManager().getTransaction();
try {
tx.begin();
myEntity = getBeanToTest().save(myEntity);
tx.commit();
} catch (Exception e) {
tx.rollback();
fail("saving failed");
}
// ...
}
maybe this'll help some of you.
Perhaps you don't have a running transaction, hence your entity isn't saved.
One option is to manually start the transaction by injecting a #PersistenceContext in the test, but better look for automatic transaction management in ejb3unit.