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
Related
I'm doing some integration tests with Spring but I'm trying to simplify my life when asserting the content of some entities that I need to resolve a lazy before.
My test is like:
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OrderIT {
#Autowired
private OrderService orderService;
#Autowired
private OrderRepository orderRepository;
#Test
public void test01CreateOrder() {
OrderDto orderDto = createSomeOrderDto();
orderService.create(orderDto);
Order order = orderRepository.findByNumber("123456");
// order asserts ...
}
}
After that we have tests on the same class that use this order (tests like changing the order, etc). This is why I'm not using #Transactional on the test method and the integration tests are executed respecting the order name, because I use the previous test as base for the next test.
This works very good. But my problem is when I need to improve my asserts trying to resolve some lazy information of the entity.
I can't just, per example, do a order.getReponsable() on my test because I will receive a LazyException error.
What works (but I don't like it)
The only way I found to do that on tests is creating a specific query on the repository fetching this responsable. I can call this query and the lazy association will no be a problem anymore. But I would not like to create repository methods just for testing purposes.
What I tried (and failed)
My idea was inject the TestEntityManager on my integration test with #AutoConfigureTestEntityManager on the test class, on the hope to access the getEntityManager and, finally, create a query on the test, like this:
Order order = (Order) testEntityManager.getEntityManager().createQuery("SELECT o FROM Order o JOIN FETCH o.responsable WHERE o.number = " + "123456").getSingleResult();
Will be perfect if worked. But the EntityManager is not available:
> java.lang.IllegalStateException: No transactional EntityManager found
There is another alternative? Again, the #Transactional on the test method is not an option for me.
I just found an alternative. I don't know if spring offer some better, but worked.
I just inject the EntityManagerFactory on my integration test class:
#Autowired
private EntityManagerFactory entityManagerFactory;
And made my query using the entity manager, but I created one:
Order order = (Order) entityManagerFactory.createEntityManager().createQuery("SELECT o FROM Order o JOIN FETCH o.responsable WHERE o.number = " + "123456").getSingleResult();
EDIT: As C. Weber suggested in the comments, the solution is to add #Transactional to the test class.
I have some tests that use an H2 in-memory DB. I need to reset the DB before each test. Although my SQL scripts are run each a test is executed, the DB is not properly reset, resulting in a missing needed entry after a delete test.
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureTestDatabase(replace=Replace.ANY, connection=EmbeddedDatabaseConnection.H2)
public class RepositoryTests {
#Autowired
private Repository repository;
#Autowired
private DataSource dataSource;
#Before
public void populateDb() {
Resource initSchema = new ClassPathResource("database/schema.sql");
Resource initData = new ClassPathResource("database/data.sql");
DatabasePopulator dbPopulator = new ResourceDatabasePopulator(initSchema, initData);
DatabasePopulatorUtils.execute(dbPopulator, dataSource);
}
#Test
public void testMethod1() {
// ...
repository.delete("testdata");
}
#Test
public void testMethod2() {
// ...
Object test = repository.get("testdata");
// is null but should be an instance
}
}
schema.sql drops all tables before recreating them. data.sql inserts all needed test data into the DB.
Running the testMethod2 alone succeeds. However, running all tests makes the test fail with a NullPointerException.
I have successfully tried to use #DirtiesContext, however this is not an option because I can't afford to have a 20 second startup for each 0.1 second test.
Is there another solution?
The Spring Test Framework provides a mechanism for the behaviour you want for your tests. Simply annotate your Test class with #Transactional to get the default rollback behaviour for each test method.
There are ways to configure the transactional behaviour of tests and also some pitfalls (like using RestTemplate inside test method), which you can read more about in the corresponding chapter of the Spring manual.
Spring Test Framework
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
I am writing unit-tests and have a quite complex setting.
A dependent bean sets up some listeners and passes them autowired services.
I want to test that the listeners are present, but not call them, so I want to pass 'null' instead of the autowired service. (specifically: I do not have setters ...)
#Autowired
SomeService1 service1
#Autowired
SomeService2 service2
public List getListeners() {
List l = new ArrayList();
l.add(new AaaListener(service1));
l.add(new BbbListener(Service2));
return l;
}
#Test
public void testListeners() {
int exptecedSize = 2;
sut.doSomething();
List l = sut.getX().getY().getListeners()
assertEquals(expectedSize,l.size());
}
Note that the SUT does depend indirectly from the class that returns the listeners.
Since this is a very small example from a big setting, I do specifically do not want to use mocks here as I want to test only presence not behavior of the listeners.
Mocking 20 or 30 of such services will slow down the tests massively.
Question: What is the easiest way to inject these nulls into the autowired instance variables?
A) Add setters ?
B) ReflectionUtils ?
C) java-config + #Beans + return null ?
They're already null when the class is instantiated ... or are you actually running them in a spring context?
You can set properties to null in the xml config like this (from the documentation)
<bean class="ExampleBean">
<property name="email"><null/></property>
</bean>
Don't use the Spring context and create the class manually
Use ReflectionTestUtils to set the field. ReflectionTestUtils allows for the setting of private fields where ReflectionUtils does not.
I'm working on a unit test which commits information through a DAO to a Oracle database, and then retrieves it and checks everything is unchanged.
There are other unit tests in the test class, and at the top of the class I have:
#TransactionConfiguration (defaultRollback = true)
I'd like to know how I can remove the #NotTransactional. I'm not specifying Transactional on the class, so no tests should be like that by default. Since this is it's own test, I don't know if the #BeforeTransaction (or After) annotations would be correct.
The biggest problem is that without the #NotTransactional, it appears as though the unsubscribe() function isn't ran. (the trash flag is unchanged.)
Running the test again, with #rollback=false and #NonTransactional present, I see the trash flag correctly set to true, in the database after the test is finished.
#RunWith (SpringJUnit4ClassRunner.class)
#TransactionConfiguration (defaultRollback = true)
public class DatabaseTest {
#Autowired (required = true)
private FooDao<Foo> fooDao;
#Autowired (required = true)
private FooService fooService;
// other tests here that are marked #Transactional.
#Test
#NotTransactional
public void testDelete() {
Foo foo = new foo();
foo.setTrashFlag(false);
foo.setId(123);
fooDao.create(foo);
Foo fooFromDb = fooService.getFromDbById(123);
assertNotNull(fooFromDb);
fooService.unsubscribe(123); // among other things,
// **sets trash flag to true.**
// sqlSession.flushStatements(); // doesn't seem to commit the unsubscribe action
// getFromDbById() returns ONLY Foos with trash set to false, so we expect
// nothing returned here as we've just set trash to true, using unsubscribe().
Foo trashedFoo = fooService.getFromDbById(123);
assertNull(trashedFoo);
Thanks!
I don't know if this is really on-topic, but I used #NotTransactional for methods that had a transactional base class. Spring says to split the tests in transactional and non-transactional, but I found out that in my case simply adding the following to the method just disabled transactions and let me delete the deprecated annotation:
#Transactional(propagation = Propagation.NEVER)
I thought I just put it out here for people who run in the same thing I did.
#Rollback(false) isn't always sufficient, it only means the transaction won't be rolled back at the end of test. But it still runs the test in a transaction, which sometimes lead to conflicts with other running transactions.
As Spring suggest, you have two options:
Split the test class into two, the transactional tests can be a in test class annotated with #Transactional, while the non-transactional test in a test class without transactional annotation.
Use method annotations instead of class annotation, annotate each transaction test with #Transactional, but remove it from the class scope
Quoting Spring documentation:
As of Spring 3.0, #NotTransactional is deprecated in favor of moving
the non-transactional test method to a separate (non-transactional)
test class or to a #BeforeTransaction or #AfterTransaction method. As
an alternative to annotating an entire class with #Transactional,
consider annotating individual methods with #Transactional; doing so
allows a mix of transactional and non-transactional methods in the
same test class without the need for using #NotTransactional.