My spring-data-jpa backend has a class that populates the (test) database with a lot of test data. The class usese the spring data repositories to create entities. All my entities have a field annotated with #CreatedData and the corresponding #EntityListeners(AuditingEntityListener.class) annotation on the model class. This works fine so far. dateCreated is automatically set correctly.
But when running Junit test I sometimes need to create a (test) object with a dateCreated in the past. How can I archive this? Only via plain JDBC?
In case you are using Spring Boot, you can mock dateTimeProvider bean used in EnableJpaAuditing annotation. Spring Data uses this bean to obtain current time at the entity creation/modification.
#Import({TestDateTimeProvider.class})
#DataJpaTest
#EnableJpaAuditing(dateTimeProviderRef = "testDateTimeProvider")
public class SomeTest {
#MockBean
DateTimeProvider dateTimeProvider;
...
It is necessary to define actual testDateTimeProvider bean, but it won't be used at all, as you will use mock instead.
You can write mockito methods afterwards as usual:
#Test
public void shouldUseMockDate() {
when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2020, 2, 2, 0, 0, 0)));
... actual test assertions ...
I found a way that works for me (using plain JDBC):
First I create my domain objects for testing with spring-data-jpa:
MyModel savedModel = myRepo.save(myModel);
That automatically fills the "dateCreated" with timestamp of "now". Since I need creation dates in the past for testing I manually tweak them with plain JDBC:
#Autowired
JdbcTemplate jdbcTemplate;
[...]
// This syntax is for H2 DB. For MySQL you need to use DATE_ADD
String sql = "UPDATE myTable SET created_at = DATEADD('DAY', -"+ageInDays+", NOW()) WHERE id='"+savedLaw.getId()+"'";
jdbcTemplate.execute(sql);
savedModel.setCreatedAt(new Date(System.currentTimeMillis() - ageInDays* 3600*24*1000);
Do not forget to also setCreatedAt inside the returned model class.
I do not know exact test case which you are using, but i can see few solutions:
create a mock object, once the dateCreated is called return date
month ago
maybe use in-mem db, populate it with date before test
go with AOP from the link provided in comments
Related
In my app (Spring Boot based) I am using Hibernate and have custom repository like that:
#Repository
public interface MyRepository extends JpaRepository<MyRepoEntity, Long> {
#Query(value="SELECT NEXTVAL('mytable')", nativeQuery=true)
Long nextId();
#Procedure(procedureName = "SCHEMA2.save_2", outputParameterName = "res")
String callProcedure(#Param("prm_nrid") Long nr);
}
In my manager have a method with the following business logic:
#Transactional
String invokeProcedure1() {
Long id = myRepo.nextId();
return myRepo.callProcedure(id);
}
The problem is that Hibernate performs the two actions randomly and out of order because there is no db "relationship".
Is there a way (preferably without explicitly using flush()) to have nexId invoked before callProcedure?
Thank you all!
These are native queries which are executed immediately. I don't know how your real application looks like, but the code you posted will first run the nextval query and only then call the stored procedure.
I'm using Spring Boot, Spring Data JPA, and JUnit 5.
All IDs in entities are automatically generated by DB (#GeneratedValue).
So all entities don't include any constructors and setter methods for initializing ID.
When I implement unit tests for controllers (#WebMvcTest),
all ids of entities are null.
The problem is that some endpoints respond id, so when I implement unit tests for them, they respond null, that is, fail the test.
How can I implement unit tests for controllers that respond ID generated by DB?
You can add Getter methods to class and then use ObjectMapper to set id field.
Employee employee = new ObjectMapper().readValue(""{\"id\": \"1\"}", Employee.class);
Other approach is to use reflection in JUnit to set that field.
Employee employee = new Employee();
Field field = Employee.class.getDeclaredField("id");
field.setAccessible(true);
field.set(employee, "1");
Other ways to use reflection: Access a private field for a junit test
I have a problem. After having created a Spring Boot project with Eclipse and configuring the application.properties file, my collections are not created, whereas after execution, the Eclipse console signals that the connection to MongoDB has been carried out normally. I don't understand what's going on. With MySQL we had the tables created so I expected the creation of the collections, but nothing.
Summary, I don't see my collection (class annoted #Document) in MongoDB after deployment.
New collection won't be created until you insert at least one document. Refer the document Create Collection
You could do this in two ways through Spring. I tested the instructions below in Spring 2.1.7.
With just a #Document class, Spring will not create a collection in Mongo. It will however create a collection if you do the following:
You have a field you want to index in the collection, and you annotate it as such in the Java class. E.g.
#Indexed(unique = true)
private String indexedData;
Create a repository for the collection:
public interface MyClassRepository extends MongoRepository<MyClass, String> {
}
If you don't need/want an index, the second way of doing this would be to add some code that runs at startup, adds a dummy value in the collection and deletes it again.
#Configuration
public class LoadDatabase {
#Bean
CommandLineRunner initDb(MyClassRepository repository) {
// create an instance of your #Document annotated class
MyClass myDocument = new MyClass();
myDocument = repository.insert(myDocument);
repository.delete(myDocument);
}
}
Make sure your document class has a field of the correct type (String by default), annotated with #Id, to map Mongo's _id field.
In the following test code snippet, values of number_of_days.last and number_of_months.plan are not getting fetched from the configuration file.Please check and see what could be the reason.When i remove #Value annotation from my service class ShiftPlanService.java and just initialize the values there with the required values,the test passes.
#ExtendWith(MockitoExtension.class)
#ContextConfiguration(classes=SpringbootMysqlExampleApplication.class)
#TestPropertySource(locations="src/main/resources/application.properties",properties= {"number_of_days.last= 7","number_of_months.plan= 2"})
class ShiftPlanServiceTest {
#Mock
ShiftPlanRepo mockedSpr;
#Mock(lenient = true)
ShiftDetailsRepo mockedSdr;
#Mock(lenient = true)
EmployeeDetailsRepo mockedEdr;
#Spy
ShiftPlanService sps;
#BeforeEach
public void setUp() {
when(mockedSdr.findShiftNameById(1)).thenReturn("Morning");
when(mockedSdr.findShiftNameById(2)).thenReturn("Afternoon");
when(mockedEdr.getNameById(0)).thenReturn("Amit");
when(mockedEdr.getNameById(1)).thenReturn("Anupam");
when(mockedEdr.getNameById(2)).thenReturn("Chirag");
when(mockedEdr.getNameById(3)).thenReturn("Rashmi");
when(mockedEdr.count()).thenReturn(4L);
}
#Test
public void testCreateShiftPlan() {
sps.createShiftPlan(4, 1, 2020);
verify(mockedSpr, times(36)).save(any(ShiftPlan.class));
verifyNoMoreInteractions(mockedSpr);
}
}
application.properties file is as follows-
server.port=8104
number_of_days.last= 7
number_of_months.plan= 2
spring.datasource.url=<<sensitive info>>
spring.datasource.username=<sensitive info>
spring.datasource.password=<sensitive info>
#Keep the connection alive while idle for a long time
spring.datasource.testWhileIdle= true
spring.datasource.validationQuery= SELECT 1
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
# Use spring.jpa.properties.* for Hibernate native properties (the prefix is
# stripped before adding them to the entity manager)
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
In the ShiftPlanService class, i have
#Value("${number_of_days.last}")
public int ndl;
#Value("${number_of_months.plan}")
public int nm;
I think you are intending to use a real instance of ShiftPlanService and have mocks injected. You need to have Spring autowire the ShiftPlanService into your test and tell it to inject the mocks like this:
#Autowired
#InjectMocks
ShiftPlanService sps;
Though you may consider just instantiating ShiftPlanService yourself in your setup method and just pass your mocks and set the other properties on the ShiftPlanService instead.
You are confusing Mockito injection with Spring injection. #Value is a Spring concept and will only be injected when Spring manages the bean, but the instance of ShiftPlanService you have in your test is injected by Mockito using #Spy (which as has been pointed out you don't really need).
My recommendation would be to decide what you want - a unit test with mocks, or a full-blown Spring test with the application context running. It seems to me that your intent is to write a unit test with everything mocked out, in which case:
remove #ContextConfiguration and #TestPropertySource (you don't need those for unit tests)
use #InjectMocks instead of #Spy on ShiftPlanService sps - it will most likely do what you want, depending on how ShiftPlanService is implemented
manually set the config values you need in sps; you can add setters for those for the test to use; if the unit test is in the same package as the class - which is a good practice - they can be package-private too - because in production Spring will autowire them for you, so you only need them for the test
oh, and keep the #Value annotation in your ShiftPlanService - it's needed for production - as explained above
We invented a Mockito extension that allows for easy injection of String/Integer/Boolean properties. Checkout the readme, it's super easy to use and works along with #InjectMocks
https://github.com/exabrial/mockito-object-injection
I have a requirement to write a simple application to write some values to DB.
Basically this is some repetitive task which has to be done quite often and I want to build a simple Spring boot app with a UI exposed so that it can be done in an automatic way.
I have an Entity Class with a simple POJO MyClient and I have written a Controller and Service Classes and am able to GET and POST To DB:
My App.properties looks like below:
spring.datasource.url=jdbc:oracle:thin:#db-host-1:1521/xxx.xx.intern
spring.datasource.username=root
spring.datasource.password=root
//Controller Class
#GetMapping("/clients")
public List<MyClient> retrieveAllClientVersions(){
return myClientService.listAllClientVersions();
}
#RequestMapping(value = "/client/add", method = RequestMethod.POST)
#ResponseBody
void addNewClientVersion(#RequestBody MyClient myClient){
myClientService.addNewClientVersion(myClient);
}
//Service Class
private MyClientRepository myClientRepository;
#Autowired
public MyClientService(MyClientRepository myClientRepository){
this.myClientRepository=myClientRepository;
}
public List<MyClient> listAllClientVersions(){
List<MyClient> myClients=new ArrayList<>();
myClientRepository.findAll().forEach(myClients::add);
return myClients;
}
public void addNewClientVersion(MyClient myClient){
myClient.setReleaseKeyVersion(RELEASE_KEY_VERSION);
myClient.setClientVersion(myClient.getClientVersion());
myClient.setDescription(DESCRIPTION);
myClient.setReleaseCertDn(DGV_RELEASE_CERT_DN);
myClient.setStatus(STATUS);
myClient.setClientSecurityProfileDbId(CLIENT_SECURITY_PROFILE_DB_ID);
myClient.setIssuerDbId(ISSUER_DB_ID);
myClientRepository.save(myClient);
}
We have around 50 test environments where I need to run the same query. I wanted to create a UI where I can have check boxes against all environment with buttons like GET and POST.
Whatever environment user selects from check boxes and say POST the "Insert" should run on all those environments.
How can this be handled? Is there a way that based on Query Parameter in POST Request the Insert can be run on different DB. How do we connect to different DB at runtime? What could be best way to do this?