I have a simple rest API using spring boot 2 and Java 11, and I have some integration tests. Some of the tests insert into the DB and assert that the data is read from the API, and some create objects via the API and assert they can be read back from the DB or the API.
The latter tests, which create objects via the API, seem to not be reseting the auto-increment value in the test h2 database. The symptom is that the tests pass independently, but not when run as a group - the IDs are the sum of all inserts so far.
I have an #Before method that drops and recreates every table, and an #After method that drops every table, and I've verified that they are running.
There are lots of questions about this but none of the suggested answers make any difference. I have tried:
Decorating the test class with #DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
Decorating the test methods with #Transactional
Running ALTER TABLE person ALTER COLUMN id RESTART WITH 1; repeatedly, including after the table creation and again right before each object creation
My code looks like this:
IntegrationTest.java
#EnableConfigurationProperties
#RunWith(SpringJUnit4ClassRunner.class)
#PropertySource(value = "classpath:application.properties")
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public abstract class IntegrationTest {
#Autowired
private DataSource dataSource;
#Autowired
JdbcTemplate jdbcTemplate;
RestTemplate restTemplate = new RestTemplate();
#Before
public void SetUp() throws SQLException {
ScriptUtils.executeSqlScript(dataSource.getConnection(), new FileSystemResource("src/test/resources/initialization.sql"));
}
#After
public void CleanUp() throws SQLException {
ScriptUtils.executeSqlScript(dataSource.getConnection(), new FileSystemResource("src/test/resources/cleanup.sql"));
}
}
PersonTest.java
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class PersonTest extends IntegrationTest {
#Test
#Transactional
public void TestCreatePerson() {
String url = "http://localhost:8080/people";
Person p = new Person();
p.setFirstName("Joe");
p.setLastName("Smith");
Person r = restTemplate.postForObject(url, p, Person.class);
Assert.assertEquals(1, (long) r.getId());
}
#Test
#Transactional
public void TestUpdatePerson() {
jdbcTemplate.execute("alter table person alter column id restart with 1");
String url = "http://localhost:8080/people";
Person p = new Person();
p.setFirstName("Joe");
p.setLastName("Smith");
Person r = restTemplate.postForObject(url, p, Person.class);
Assert.assertEquals(1, (long) r.getId()); // FAILS HERE BECAUSE ID == 2
r.setLastName("Jones");
restTemplate.put(url, r);
url = "http://localhost:8080/people/1";
Person u = restTemplate.getForObject(url, Person.class);
Assert.assertEquals("Jones", u.getLastName());
}
initialization.sql
DROP TABLE IF EXISTS person;
CREATE TABLE IF NOT EXISTS person (
id int(11) NOT NULL AUTO_INCREMENT,
first_name varchar(128) NOT NULL,
last_name varchar(128) NOT NULL
);
-- for good measure
ALTER TABLE person ALTER COLUMN id RESTART WITH 1;
cleanup.sql
DROP TABLE IF EXISTS person;
Any ideas what I am doing wrong?
Related
I am trying to test this method:
#Query(value = "select * from table "
+ "where match(name_column) AGAINST(:name in BOOLEAN MODE)", nativeQuery = true)
List<ManagedGroup> findByGroupNameMatches(#Param("name") String groupName);
The test itself :
#Test
public void findByGroupNameMatches_should_return_when_contains_part_of_name(){
ManagedGroup managedGroup = prepearGroup(UUID.randomUUID().toString());
managedGroup.setGroupName("def123");
testEntityManager.persistAndFlush(managedGroup);
// in desperate tries - tried to save it via repositroty as well
groupRepository.save(managedGroup);
//the groups can be found - there is two groups with names 'def123' and 'default name'
List<ManagedGroup> res = groupRepository.findAll();
Pageable pageable = new OffsetLimitPageRequest(0, 10, Sort.by(Sort.Direction.DESC, "id"));
List<ManagedGroup> managedGroupPage = groupRepository.findByGroupNameMatches("+def*");
//managedGroupPage is empty after the method is called
Assertions.assertEquals(defaultManagedGroup, managedGroupPage.get(0));
}
The test fails cause I get empty result from findByGroupNameMatches. I've tried saving and retriviengthe entity via jdbcTemplate - still no result. Other tests work as well, so I guess that it is not the problem.
The method works in implemented controller and is already tested by QA team.
I've also tried to run the test against QA db and it works with the entities, persisted before tests are booted. I am sure that the problem is with persisting it, but I still cannot find the solution. I've tried different combinations of flush and etc.. and still no result
Providing more info to reproduce the test:
static final MySQLContainer DATABASE = new MySQLContainer("mysql:8.0.23");
static {
DATABASE.start();
}
static class Initializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext context) {
TestPropertyValues.of(
"spring.datasource.url=" + DATABASE.getJdbcUrl(),
"spring.datasource.username=" + DATABASE.getUsername(),
"spring.datasource.password=" + DATABASE.getPassword()
).applyTo(context.getEnvironment());
}
The test class is annotated with
#DataJpaTest
#ExtendWith(SpringExtension.class)
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#DisabledIfEnvironmentVariable(named = "RUN_INTEGRATION_TEST", matches = "false")
#DirtiesContext
}
Hello shouldnt you have a table name for this native query
#Query(value = "select * from table TABLE_NAME "
+ "where match(name_column) AGAINST(:name in BOOLEAN MODE)", nativeQuery = true)
List<ManagedGroup> findByGroupNameMatches(#Param("name") String groupName);
Also you should flush when Transactional annotation not there
// in desperate tries - tried to save it via repositroty as well
groupRepository.save(managedGroup);
use groupRepository.flush or saveAndFlush
In my project we are caching the data of a table where we use #NamedQuery for fetching the data (Hibernate queries) and we are also using #NamedNativeQueries and my code is like that
#SpringBootTest
public class NonPolygonIntegeration {
#Autowired
private Repository localRepository;
#MockBean
ServicePropertiesRepository servicePropertiesRepository;
#MockBean
CacheService alendarCacheService;
#Autowired
OlygonService olygonService;
#Test
#Order(1)
#Sql(scripts = {"/sqls/mdm.sql", "/sqls/p.sql","/sqls/q.sql",
"/sqls/x.sql",
"/sqls/y.sql",
"/sqls/z.sql"
}, config = #SqlConfig(encoding = "utf-8"))
void testGetAllActiveCurrencies_forDataPresentInDb() {
}
}
"""
I have data in my local database for all the corresponding tables in use.
query which will get in use are
#Query("Select * FROM Properties WHERE id.Code =:serviceCode AND (endDate IS NULL OR endDate >= CURRENT_DATE)")
and other is
select * from empoyee_table where employee_id = :id
How to handle such scenario?
I am trying to write the unit test for a method which does not take any parameter and inside it uses a mongoTemplate to query using criteria. Let me post the entity class first
#Data
#Builder
#Getter
#Setter
#NoAgrsConstructor
#EqualAndHashCode
public class MyInventory{
#Id
private String id;
#Field("it_key")
#JsonProperty("it_key")
private String it_key;
............
In my service class it is feching data using the below method
public List<MyInentory> getAllData(){
Query query = new Query();
//some criteria here
query.addCriteria(criteria);
List<MyInventory> listOfInventory = mongoTemplate.find(query,MyInventory.class);
return listOfInventory;
}
I am trying to write the unit test for it
#RunWith(SpringRunner.class)
#ExtendWith(MockitoExtension.class)
#MockitoSettings(strictness=Strictness.LENIENT)
#SpringBootTest(classes={MongoTemplate.class}
#ActiveProfiles("mock")
public class MyInventoryServiceTest{
#MockBean
private MongoTemplate mongoTemplate;
#Test
public void testGetAllData(){
mockMyInentoryList.add(MyInventory.builder()
.id("614a4d70cghyt86")
.it_key("DESS")
//.........setting the data here
.build()
);
List<MyInventory> expected = new ArrayList<>();
Inventory inv = Mockito.mock(MyInventory.class);
expected.add(inv);
when(this.mongoTemplate.find(any(Query.class),eq(MyInventory.class)))
.thenReturn(mockMyInentoryList);
Assert.asserEquals(mockMyInentoryList,expected);
And the error what i get is
java.lang.AssertionError
Expected:[MyInventory(id=614a4d70cghyt86,it_key=DESS........]
Actual: [Mock for MyInventory, hashcode: 1252142274]
You should call getAllData() in your test and verify that the results returned match the results returned from the mocked Mongo template:
The test will look something like this:
#Test
public void testGetAllData() {
MyInventoryService myInventoryService = new MyInventoryService(mongoTemplate);
List<MyInventory> mockMyInventoryList = new ArrayList<>();
mockMyInventoryList.add(MyInventory.builder()
.id("614a4d70cghyt86")
.it_key("DESS")
//.........setting the data here
.build()
);
when(this.mongoTemplate.find(any(Query.class), eq(MyInventory.class)))
.thenReturn(mockMyInventoryList);
Assert.assertEquals(mockMyInventoryList, myInventoryService.getAllData());
}
I'm using:
Quarkus with JPA (javax)
Postgres 11 database
I have:
An Entity
#Entity
#Table(name = "MyEntityTable")
#NamedQuery(name = MyEntity.DOES_EXIST, query = "SELECT x FROM MyEntity x WHERE x.type = :type")
public class MyEntity {
public static final String DOES_EXIST = "MyEntity.DoesExists";
#Id
#SequenceGenerator(name = "myEntitySequence", allocationSize = 1)
#GeneratedValue(generator = myEntitySequence)
private long id;
#Column(name = type)
private String type;
}
A repository
#ApplicationScoped
#Transactional(Transactional.TxType.Supports)
public class MyEntityReporitory {
#Inject
EntityManager entityManager;
#Transactional(Transactional.TxType.Required)
public void persist(final MyEntity entity) {
entityManager.persist(entiy);
}
public boolean doesExist(final String type) {
final TypedQuery<MyEntity> query = entityManager
.createNamedQuery(MyEntity.DOES_EXIST, MyEntity.class)
.setParameter("type", type);
return query.getResultList().size() > 0;
}
}
A test with two variations
Variation 1
#QuarkusTest
#QuarkusTestResource(DatabaseResource.class) // used to set up a docker container with postgres db
public class MyEntityRepositoryTest {
private static final MyEntity ENTITY = entity();
#Inject
MyEntityRepository subject;
#Test
public void testDoesExist() {
subject.persist(ENTITY);
final boolean actual = subject.doesExist("type");
assertTrue(actual);
}
#Test
public void testDoesExist_notMatching() {
subject.persist(ENTITY);
final boolean actual = subject.doesExist("another_type");
assertFalse(actual);
}
private static MyEntity entity() {
final MyEntity result = new MyEntity();
result.setType("type")
return result;
}
}
When I execute this test class (both tests) I'm getting the following Exception on the second time the persist method is called:
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist com.mypackage.MyEntity
...
Variation 2
I removed the constant ENTITY from the test class, instead I'm calling now the entity() method inside the tests, like:
...
subject.persist(entity());
...
at both places. Now the Exeption is gone and everything is fine.
Question
Can someone explain to me, why this is the case (why variante 2 is working and variante 1 not)?
https://vladmihalcea.com/jpa-persist-and-merge/
The persist operation must be used only for new entities. From JPA perspective, an entity is new when it has never been associated with a database row, meaning that there is no table record in the database to match the entity in question.
testDoesExist executed, ENTITY saved to database and ENTITY.id set to 1
testDoesExist_notMatching executed and persist called on ENTITY shows the error beacuse it exists in the database, it has an id assigned
The simplest fix is to call entity() twice, as in you variation 2.
But don't forget that the records will exist after a test is run, and might affect your other test cases. You might want to consider cleaning up the data in an #After method or if you intend to use this entity in multiple test cases then put the perist code into a #BeforeClass method.
In my Spring Batch Application, I am reading, processing and then trying to write with a ItemWriter to the database using stored procedure:
Below is what my CSV file looks like lets say which I want to read, process and write:
Cob Date;Customer Code;Identifer1;Identifier2;Price
20180123;ABC LTD;BFSTACK;1231.CZ;102.00
My ItemWriter:
#Slf4j
public class MyDBWriter implements ItemWriter<Entity> {
private final EntityDAO scpDao;
public MyWriter(EntityDAO scpDao) {
this.scpDao = scpDao;
}
#Override
public void write(List<? extends Entity> items) {
items.forEach(scpDao::insertData);
}
}
My DAO implementation:
#Repository
public class EntityDAOImpl implements EntityDAO {
#Autowired
private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall simpleJdbcCall = null;
#PostConstruct
private void prepareStoredProcedure() {
simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate).withProcedureName("loadPrice");
//declare params
}
#Override
public void insertData(Entity scp) {
Map<String, Object> inParams = new HashMap<>();
inParams.put("Identifier1", scp.getIdentifier1());
inParams.put("Identifier2", scp.getIdentifier1());
inParams.put("ClosingPrice", scp.getClosingPrice());
inParams.put("DownloadDate", scp.getDownloadDate());
simpleJdbcCall.execute(inParams);
}
}
My Stored procedure used to update is as follows:
ALTER PROCEDURE [dbo].[loadPrice]
#Identifier1 VARCHAR(50),
#Identifier1 VARCHAR(50),
#ClosingPrice decimal(28,4),
#DownloadDate datetime
AS
SET NOCOUNT ON;
UPDATE p
SET ClosingPrice = #ClosingPrice,
from Prices p
join Instrument s on s.SecurityID = p.SecurityID
WHERE convert(date, #DownloadDate) = convert(date, DownloadDate)
and s.Identifier1 = #Identifier1
if ##ROWCOUNT = 0
INSERT INTO dbo.Prices
(
sec.SecurityID
, ClosingPrice
, DownloadDate
)
select sec.SecurityID
, #ClosingPrice
, LEFT(CONVERT(VARCHAR, #DownloadDate, 112), 8)
from dbo.Instrument sec
WHERE sec.Identifier1 = #Identifier1
Give I have this setup, one of my requirement is that if I am unable to update/insert to the database using #Identifier1 i.e. there is no SecurityID which matched with Identifier1, I need to THEN update/insert
using the Identifier2. Second level match if you like.
How can I do this in my DAO insertData()? It is business logic and prefer in java code instead of stored proc but I am keen to look at your examples how this can be achieved.
How can I return a result of a row being updated/inserted and take decision as to whether or not to update/insert with second identifier?
For the update I would change the where clause to
WHERE convert(date, #DownloadDate) = convert(date, DownloadDate)
and (s.Identifier1 = #Identifier1 OR s.Identifier2 = #Identifier2)
and for the insert
WHERE sec.Identifier1 = #Identifier1 OR sec.Identifier2 = #Identifier2
That should work even if I haven't verified it myself. I am assuming that the given values for identifier1 and identifier2 can not match two different rows in the Instrument table.