I have a Spring Boot application (written in Kotlin) which exposes a REST endpoint through a Controller QueueController. I'm testing that controller in a SpringBootTest by calling that endpoint using MockMvc as follows (some boilerplate omitted for readability):
// QueueControllerIT.kt
#SpringBootTest
#AutoConfigureMockMvc
#EnableAutoConfiguration(
exclude = [
JpaRepositoriesAutoConfiguration::class,
DataSourceAutoConfiguration::class,
HibernateJpaAutoConfiguration::class
]
)
#DirtiesContext
internal class QueueControllerIT {
lateinit var mockmvc: MockMvc
#Autowired
lateinit var wac: WebApplicationContext
#BeforeEach
fun createStubs() {
mockmvc = MockMvcBuilders.webAppContextSetup(wac).build()
}
#Test
fun `calling my endpoint`() {
mockmvc.perform(
MockMvcRequestBuilders.post("/stores/1/zones/one2one/customers/last")
).andExpect(status().isNoContent)
}
}
The problem is that when calling that endpoint requests to a DB are made. The DB is modelled using JPA-Entities and CRUD-Repositories, e.g. as follows:
interface StoreRepository : CrudRepository<Store, Int> {
fun findByExternalId(externalId: String): Store?
}
#Entity
class Store {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int? = null
#Column(name = "external_id")
lateinit var externalId: String
// more columns and relationships to other tables here
}
I am currently mocking the repositories and entities using Mockk as follows:
// QueueControllerIT.kt
#MockkBean
lateinit var storeRepositoryMock: StoreRepository
#Test
fun `calling my endpoint`() {
val store = Store()
every { storeRepositoryMock.findById(storeId) } returns Optional.of(store)
mockmvc.perform(
MockMvcRequestBuilders.post("/stores/1/zones/one2one/customers/last")
).andExpect(status().isNoContent)
}
However, that produces the following error:
kotlin.UninitializedPropertyAccessException: lateinit property externalId has not been initialized
Of course I could now mock/stub all the lateinit properties of store until my test succeeds, but there must be an easier way. I read a bit about #DataJpaTest and I think it would actually be exactly what I need, because it performs read/writes real entities to an in-memory database. However just adding #DataJpaTest to my test does not seem to work well with the existing setup above.
:
java.lang.IllegalStateException: Configuration error: found multiple declarations of #BootstrapWith for test class [com.swisscom.oce.bouncer.controller.queue.QueueControllerIT]: [#org.springframework.test.context.BootstrapWith(value=org.springframework.boot.test.context.SpringBootTestContextBootstrapper), #org.springframework.test.context.BootstrapWith(value=org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper)]
What am I doing wrong? How can I test a Spring Boot application using #SpringBootTest so I have a real application context and at the same time I don't have to worry about mocking/stubbing away the whole persistence layer?
I believe that you can annotate the test class with #Transactional and the database data will be reverted after each test :)
So you won't have to mock anything repository-related.
Related
I use spring boot 3
Main spring boot class
#EnableTransactionManagement
#SpringBootApplication
#Slf4j
public class FlexApplication{
private final ApplicationParameterManager appParamManager;
public FlexApplication(ApplicationParameterManager appParamManager) {
this.appParamManager = appParamManager;
}
#PostConstruct
public void init(){
}
....
}
#Service
#Slf4j
public class ApplicationParameterManager{
....
}
Basic test
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ListUserRepositoryTest {
#Autowired
private ListUserRepository repository;
#Test
public void getListUserByUserType(){
String typeUser = "CETEST";
Pageable page = Pageable.ofSize(10);
Page<ListUser> pageListUser = repository.findAllByTypeUser(typeUser, page);
assertThat(pageListUser.getContent().size() > 5 ).isTrue();
}
}
Otherwise this test, application run well
I get this error
Parameter 0 of constructor in com.acme.FlexApplication required a bean
of type 'com.acme.parameter.ApplicationParameterManager' that could
not be found.
I think it is not related to version of Spring Boot.
As you're using #DataJpaTest , your bean is not created
Spring Docs:
#DataJpaTest can be used if you want to test JPA applications. By
default it will configure an in-memory embedded database, scan for
#Entity classes and configure Spring Data JPA repositories. Regular
#Component beans will not be loaded into the ApplicationContext.
Solution would be to use #SpringBootTest instead of #DataJpaTest if your test is not really a JPA test.
Also, still using #DataJpaTest you could add #Import(ApplicationParameterManager.class) to your test class
When using #DataJpaTest, you are not creating the whole spring context as when you run the application normally but you only create the beans responsible for data access layer.
In order to run your tests properly, you need to provide a mocked bean of type ApplicationParameterManager.
The easiest way to do it is by utilizing #MockBean annotation.
So, to make your tests work, edit the test in the following way.
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ListUserRepositoryTest {
#MockBean
private ApplicationParameterManager applicationParameterManager;
#Autowired
private ListUserRepository repository;
#Test
public void getListUserByUserType(){
String typeUser = "CETEST";
Pageable page = Pageable.ofSize(10);
Page<ListUser> pageListUser = repository.findAllByTypeUser(typeUser, page);
assertThat(pageListUser.getContent().size() > 5 ).isTrue();
}
}
That way, the spring context will include a mocked bean of your required dependency.
Take a look at #MockBean java doc for more information. https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
If you prefer to run the whole spring context in order to perform full integration tests, take a look at #SpringBootTest annotation.
#DataJpaTest should be used when you want to test data access layer in isolation.
I'm trying to write unit tests for a DAO/Repository. My intent is to mock the NamedParameterJdbcTemplate object within and verify that the surrounding business logic doesn't do any bad (i.e.: handles nulls, etc...).
I'd also like to verify that the SQL is combined correctly. I can do this with doAnswer(...), but the SQL queries live in a .properties file.
Is there a way to load a .properties file for testing, without loading all of the other dependencies of this class?
What I've Tried
I've tried decorating the Test class with various permutations of :
#ExtendWith(SpringExtension.class)
#ExtendWith(MockitoExtension.class)
#ContextConfiguration(classes = WidgetRepositoryImpl.class)
#TestPropertySource(properties = "sql.properties")
However, turning these on and off always seems to have one of the following effects:
Load the NamedParameterJdbcTemplate as a bean. This could work if I specified the nearby AppConfig class in the ContextConfiguration annotation, but then I'd need to load all of its underlying dependencies, and the whole point is to mock this field.
Load the Repository and mock the NamedParameterJdbcTemplate, but not load sql.properties. The SQL statements are now null.
I want to avoid ReflectionTestUtils.setField(widgetRepository, "namedParameterJdbcTemplate", "<put the sql here>"). This works, but there are many SQL queries (the code below is very simplified). It also introduces a risk of human error (since now we're testing strings in the Test class, not the actual properties file.
The question
Is there any way to load sql.properties for this class, but not attempt to load the NamedParameterJdbcTemplate?
Unit Test
#ExtendWith(SpringExtension.class)
#TestPropertySource(properties = "sql.properties")
class WidgetRepositoryImplTest {
#Mock
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
#Autowired
private WidgetRepository widgetRepository;
#Test
public void testGetWidgetById() {
// build a mock that returns a fake widget, and stores the SQL string for other tests
doAnswer(invocation -> { ...omitted for brevity... }).when(namedParameterJdbcTemplate).query(anyString(), anyMap(), any(WidgetMapper.class));
Widget widget = widgetRepository.getWidgetById("asdf-1234");
assertNotNull(widget);
}
Repository
#PropertySource("classpath:sql.properties")
#Slf4j
#Repository
public class WidgetRepositoryImpl implements WidgetRepository {
#Value("${widget-sql.selects.select-widget-by-id}")
private String selectWidgetByIdQuery;
#Autowired private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
#Override
public Widget getWidgetById(String id) {
Map<String, String> params = new HashMap<>();
params.put("widgetId", id);
List<Widget> results = namedParameterJdbcTemplate.query(selectWidgetByIdQuery, params, new WidgetMapper());
if (results.isEmpty()) {
return null;
}
return results.get(0);
}
sql.properties
widget-sql.selects.select-widget-by-id=select * from [WIDGETS] where WIDGET_ID=:widgetId
# a few dozen additional queries in here
The solution is to use SpringExtension with the #MockBean annotation. This annotation will generate a mock implementation of the given class and provide it as a Spring bean. This means that if your class uses #Autowired, Spring will inject the mock for you:
#ExtendWith(SpringExtension.class) // Make sure to use SpringExtension
#TestPropertySource(properties = "sql.properties")
class WidgetRepositoryImplTest {
#MockBean // Replace #Mock with #MockBean
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
#Autowired
private WidgetRepository widgetRepository;
// ...
}
I'm buidling a backend that will fetch some data from several externals APIs, to populate DB after some processes, and then expose these data via a rest api. Using JPA and postgresql, in springboot.
I have created the entities, repositories and fetch external apis from a webclient.
However, I have a dependency injection issue. I read tons of articles and issues, but can not make it work. When trying to inject the repository, I got the well known error lateinit var not initialized.
I also tried constructor injection, but still doesn't work. It seems that the repository is not considered to be a bean that could be autowired.
Any help would be appreciated
FIXED SEE SOLUTION BELOW. PB WAS IN THE SERVICE
Application. Running the startuprocess from the context
#SpringBootApplication(exclude = [JacksonAutoConfiguration::class])
class SpringbootkotlinApplication
fun main(args: Array<String>) {
val context = runApplication<SpringbootkotlinApplication>(*args)
val startupProcess = context.getBean(StartupProcesses::class.java)
startupProcess.fetchGroup()
}
startup class as #component :fetches external api and calls a service to save data in db
#Component
class StartupProcesses {
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource() //<-- fetch ext api OK
SplitDataSourceToEntities().populateGroup(fetch) //<-- call service to populate db
}
}
Service that should populates the DB through repository but error in lateinit repo
#Service
class SplitDataSourceToEntities {
#Autowired
lateinit var repo:IronMaidenGroupRepository // <-- error is here
fun populateGroup(dto: DTOIronMaidenAPi): IronMaidenGroupEntity {
val dbgroup = IronMaidenGroupEntity(
groupId = dto.id!!,
name = dto.name ?: ""
)
return repo.save(dbgroup)
}
}
the repo, extending JPA repository
import com.jerome.springbootkotlin.model.dbentities.ironmaidengroupentity.IronMaidenGroupEntity
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
#Repository
interface IronMaidenGroupRepository:JpaRepository<IronMaidenGroupEntity, String> {
}
SOLUTION
Service should be defined like this (the SplitDataSourceToEntitiesclass should also be late init instead of beeing instantiated)
#Component
class StartupProcesses {
#Autowired
lateinit var splitDataSourceToEntities: SplitDataSourceToEntities // <--add this lateinit
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource()
splitDataSourceToEntities.populateGroup(fetch) //<-- fix is here
}
}
#Autowired works only when managed by Spring
It is important to note, that #Autowired does only work when the class has been initialized by Spring. In your code, you have an #Autowired annotation in class SplitDataSourceToEntities, but you manually instantiate SplitDataSourceToEntities in StartupProcesses.fetchGroup. That cannot work, because Spring had no possibility to auto-wire the lateinit var.
The problem can easily be solved by using an autowired instance of your service:
#Component
class StartupProcesses {
#Autowired
lateinit var splitDataSourceToEntities: SplitDataSourceToEntities
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource() //<-- fetch ext api OK
splitDataSourceToEntities.populateGroup(fetch) //<-- call service to populate db
}
}
Help Spring finding your JpaRepository
Additionally, you probably need to add an #EnableJpaRepositories annotation to your application:
#EnableJpaRepositories(basePackageClasses = [IronMaidenGroupRepository::class])
#SpringBootApplication(exclude = [JacksonAutoConfiguration::class])
class SpringbootkotlinApplication
...
Instead of basePackageClasses = ... you can just define the package directly by name, but the package name is not included in your example.
Do you have to use #Autowired at all?
Considering your question in the comments:
There is nothing wrong with your design, because it is not not necessary to "play" with #Component and #Autowired. You could as well get rid of the #Autowired annotations and define those variables as, e.g., constructor parameters.
That could look like this:
class SpringbootkotlinApplication
fun main(args: Array<String>) {
val context = runApplication<SpringbootkotlinApplication>(*args)
val repo = context.getBean(IronMaidenGroupRepository::class.java)
StartupProcesses(SplitDataSourceToEntities(repo))
}
#Component
class StartupProcesses(
val splitDataSourceToEntities: SplitDataSourceToEntities
) {
...
}
#Service
class SplitDataSourceToEntities(
val repo: IronMaidenGroupRepository
) {
...
}
Now only your Repository is managed by Spring, but on the flip-side you have to manage everything else by yourself which might get very tedious when your classes and their dependencies grow. It is much more comfortable (and in the end leads to better readable code) to let just Spring manage all the dependencies.
I am, as a learning experience, trying to port the tests described in Spring Boot's kotlin tutorial (https://spring.io/guides/tutorials/spring-boot-kotlin/) from using JUnit 5 to KotlinTest.
In a repositories test annotated with #DataJpaTest, I manage to autowire my JPA repositories fine. But autowiring Spring Boot's TestEntityManager does not work, as I get java.lang.IllegalStateException: No transactional EntityManager found when I try to use it in a test.
In addition to #DataJpaTest, I tried adding #Transactional, without luck.
When running the test with JUnit5, persisting an entity with the entityManager works fine.
#DataJpaTest
class RepositoriesTests #Autowired constructor(
val entityManager: TestEntityManager,
val userRepository: UserRepository,
val articleRepository: ArticleRepository) {
#Test
fun `When findByIdOrNull then return article`() {
val juergen = User(login = "springjuergen", firstname = "Juergen", lastname = "Hoeller")
entityManager.persist(juergen) <--- OK
But with KotlinTest, it fails:
#DataJpaTest
class RepositoriesTests #Autowired constructor(
val entityManager: TestEntityManager,
val userRepository: UserRepository,
val articleRepository: ArticleRepository) : StringSpec() {
init {
"When findByIdOrNull then return article" {
val juergen = User(login = "springjuergen", firstname = "Juergen", lastname = "Hoeller")
entityManager.persist(juergen) <--- FAIL
The error being java.lang.IllegalStateException: No transactional EntityManager found.
I haven't been able to figure out how to provide this entitymanager in the test implemented using KotlinTest.
Just create transaction wrapper, like this:
#Component
class TransactionWrapperUtils {
#Transactional
public fun runWithTransaction(f: () -> Unit) {
f()
}
}
And call it
transactionWrapperUtils.runWithTransaction { entityManager.persist(juergen) }
I have working kotlin service, but when i try to write test for it i get stuck because i cant get all my services initialized no matter what...
#RunWith(SpringRunner::class)
class DataServiceTest {
#InjectMocks
private lateinit var DataService : DataService
#Mock
private lateinit var updateDataService: UpdateDataService
#Test
fun shouldUpdateCustomerEmail() {
DataService.setNewCustomerEmail("221722", ApiEmail("test#test.org"))
}
}
which calls DataService class:
#Autowired
private lateinit var updateDataService: UpdateDataService
.....
fun setNewCustomerEmail(id: String, email: ApiEmail) {
updateDataService.setNewCustomerEmail(id, email)
}
which calls UpdateDataService class:
#Service
open class UpdateDataService {
#Autowired
private lateinit var addressRepository: AddressRepository
fun setNewCustomerEmail(id: String, email: ApiEmail) {
val AddressList = getCustomerAddressList(id)
mapNewEmailToAddressList(AddressList, email)
cusAddressRepository.saveAll(AddressList)
}
fun getCustomerCusbaAddressList(id: String) : List<Address> {
return addressRepository.findAddressByCustomerId( id )
}
fun mapNewEmailToAddressList(cusbaAddressList : List<Address>, email: ApiEmail) {
AddressList.map { DataUtil.trimAddressFields( it ) }
AddressList.map { it.email = email.email }
}
}
I've tried lots of different #RunWith() properties and lots of different ways to Autowire / Mock / InjectMocks but to no avail.
Problem:
At the moment with this code. AddressRepository will be uninitialized when test gets to UpdateDataService.class in line. return addressRepository.findAddressByCustomerId( id )
Question:
How do i wire all these services in such way that services are loaded when app is running, but also test would know how to wire these services and repository ?
Using Spring Boot Test you can operate context easily
#RunWith(SpringRunner::class)
#SpringBootTest
class DataServiceTest
or provide a #TestConfiguration