How to inject a service component in Spring Test class using Kotlin? - spring

I am trying to test the business logic/services. Using Kotlin in my Spring Boot application, I am trying to instantiate my services, using the test class constructor. But this is not allowed, as I am getting a ParameterResolutionException.
This is my code:
#SpringBootTest
class IntermediateApplicationTests(val ticketService: TicketService) {
#Test
fun contextLoads() {
}
}
How can I achieve this?
Thanks for every help!

You can use the #Autowired annotation to do this:
#SpringBootTest
class IntermediateApplicationTests #Autowired constructor(
val ticketService: TicketService
) {
#Test
fun contextLoads() {
}
}
In fact this is the recommended way of doing injection and the IDEA Spring plugin will complain if you do it some other way.

Related

Write Unit test in SpringBoot Without start application

Am developing MicroServices in springBoot. Am writing unit test for Service and DAO layer. When I use #SpringBootTest it starting application on build. But It should not start application
when I run unit test. I used #RunWith(SpringRunner.class), But am unable to #Autowired class instance in junit class. How can I configure junit test class that should not start application and how to #Autowired class instance in junit class.
Use MockitoJUnitRunner for JUnit5 testing if you don't want to start complete application.
Any Service, Repository and Interface can be mocked by #Mock annotation.
#InjectMocks is used over the object of Class that needs to be tested.
Here's an example to this.
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
public class AServiceTest {
#InjectMocks
AService aService;
#Mock
ARepository aRepository;
#Mock
UserService userService;
#Before
public void setUp() {
// MockitoAnnotations.initMocks(this);
// anything needs to be done before each test.
}
#Test
public void loginTest() {
Mockito.when(aRepository.findByUsername(ArgumentMatchers.anyString())).thenReturn(Optional.empty());
String result = aService.login("test");
assertEquals("false", result);
}
With Spring Boot you can start a sliced version of your application for your tests. This will create a Spring Context that only contains a subset of your beans that are relevant e.g. only for your web layer (controllers, filters, converters, etc.): #WebMvcTest.
There is a similar annotation that can help you test your DAOs as it only populates JPA and database relevant beans (e.g. EntitiyManager, Datasource, etc.): #DataJpaTest.
If you want to autowire a bean that is not part of the Spring Test Context that gets created by the annotatiosn above, you can use a #TestConfiguration to manually add any beans you like to the test context
#WebMvcTest(PublicController.class)
class PublicControllerTest {
#Autowired
private MockMvc mockMvc;
#TestConfiguration
static class TestConfig {
#Bean
public EntityManager entityManager() {
return mock(EntityManager.class);
}
#Bean
public MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
}
}
Depending your test setup, if you don't want to autowire a mock but the "real thing", You could simply annotate your test class to include exactly the classes you need (plus their transitive dependencies if necessary)
For example :
#SpringJUnitConfig({ SimpleMeterRegistry.class })
or
#SpringJUnitConfig
#Import({ SimpleMeterRegistry.class })
or
#SpringJUnitConfig
#ContextConfiguration(classes = { SimpleMeterRegistry.class })
See working JUnit5 based samples in here Spring Boot Web Data JDBC allin .

Spring boot #Import for loading config integration test not working

I have a simple spring boot app and i am implementing some integration test. I have 2 classes one that will hold my common configuration (Demo3ApplicationTests ) and the other one my integration test class(DumyClassTest), please find below it is empty for the time being:
#SpringBootTest(classes = Demo3Application.class)
class Demo3ApplicationTests {
#Test
void contextLoads() {
}
}
My integration test class:
#Import(value = Demo3ApplicationTests.class)
public class DumyClassTest{
#Autowired
DemoService demoService;
#Test
public void testImportConfig() {
demoService.logDummyMsg();
}
}
When I run the test testImportConfig the demoService value is null as I guess the #import i am not setting it up correctly. However when I extends that is DumyClassTest extends Demo3ApplicationTests the demoService is not null and the test is run correctly.
Any idea why when I use the import annotation the demoService is null?
Thanks in advance.

Spring Boot testing with different service class

I have a pretty basic question, apologies if it has been asked before. I fear I may not be using the right words, this is my first rodeo with Spring.
I have a RestController declared as such:
#RestController
class TelemetryController {
#Autowired
lateinit var service: TelemetryService
//...
}
with a concrete implementation of TelemetryService as such in our main module:
#Service
class ConcreteTelemetryService : TelemetryService {
// some production code
}
I then have a service I want to use in my controller during tests (inside our test module:
#Service
class TestingTelemetryService : TelemetryService {
// some test code using local data
}
Critically, I have do NOT want to use Mockito for this, as the implementation of the tests require very specific setup that is not appropriate for Mockito.
My test is declared as such:
#RunWith(SpringRunner::class)
#SpringBootTest
#AutoConfigureMockMvc
class HowDoInjectServiceExampleTest {
#Autowired
lateinit var mockMvc: MockMvc
}
How do I get my TestingTelemetryService inside my controller in this instance?
There are various way to achieve this but I would recommend to use Spring Profiles.
Use the default profile with the concrete implementation. This bean will be used if no profile is specified.
#Profile("default")
#Service
class ConcreteTelemetryService : TelemetryService {
// some production code
}
Add the profile "test" to the test implementation.
#Profile("test)
#Service
class TestingTelemetryService : TelemetryService {
// some test code using local data
}
Now you can start your test with
-Dspring.profiles.active=test
Read more about profiles here:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html
If your TestingTelemetryService is in same package as HowDoInjectServiceExampleTest then you can simply autowire test bean like
#RunWith(SpringRunner::class)
#SpringBootTest
#AutoConfigureMockMvc
class HowDoInjectServiceExampleTest {
#Autowired
lateinit var mockMvc: MockMvc
#Autowired
var service: TestingTelemetryService
}
if not then you should define some TestConfiguration and programatically define bean with service name and use it using #Qualifier in test to resolve which bean to use (in your case its test bean)

Is there a way to include a spring component in a WebMvcTest

Given production code classes:
#RestController
#RequiredArgsConstructor
public class MyController {
private final MyValidator validator;
// annotations relating to request mapping excluded for brevity
public void test(#Valid #RequestBody final MyParams params) {
// do stuff
}
#InitBinder
#SuppressWarnings("unused")
protected void initBinder(final WebDataBinder binder) {
binder.setValidator(validator);
}
}
and
#Component
#RequiredArgsConstructor
public class MyValidator implements Validator {
...
#Override
public void validate(final Object target, final Errors errors) {
// custom validation
}
}
and finally test code:
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class MyControllerTest {
// tests
}
I encounter the error:
NoSuchBeanDefinitionException: No qualifying bean of type 'MyValidator' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
I think the error is fair enough. I've annotated the test as a WebMvcTest, which I believe has excluded #Component beans. This is intentional and desired (from the perspective that I am only wanting to test the "web layer", not the whole context - it just so happens I need a component which is related/used only in the controllers)
My question, therefore, is: how can one explicitly include a component like a validator in the test context for a web test?
My environment is java version "10.0.2" 2018-07-17, spring boot 1.5.16.RELEASE.
There are two ways to solve this.
Using #SpringBootTest and #AutoConfigureMvc instead of #RunWith(SpringRunner.class) and #WebMvcTest.
#SpringBootTest
#AutoConfigureMvc
public class MyControllerTest {
}
Creating a #TestConfiguration class that injects the 'MyValidator' bean as:
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class MyControllerTest {
#TestConfiguration
static class TestConfig {
#Bean
MyValidator getMyValidator(){
return new MyValidator();
}
}
// tests
}
More on this can be found here : https://mkyong.com/spring-boot/spring-boot-how-to-init-a-bean-for-testing/
There are two ways to test the web layer
first.
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyControllerTest {
#Autowired
private MyController myController;
}
The #SpringBootTest annotation tells Spring Boot to go and look for a
main configuration class (one with #SpringBootApplication for
instance), and use that to start a Spring application context.
A nice feature of the Spring Test support is that the application
context is cached in between tests, so if you have multiple methods in
a test case, or multiple test cases with the same configuration, they
only incur the cost of starting the application once. You can control
the cache using the #DirtiesContext annotation.
Secondly, if you want to use the #WebMvcTest(MyController.class)
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class MyControllerTest {
#MockBean
private MyValidator validator;
}
But this validator is a fake, so you have to customize it for testing.
See this link for more details https://spring.io/guides/gs/testing-web/
I cannot recommend it as a standard practice but if you do need an instance of a dependency in your Web MVC tests (for example in legacy code) you can add them into the spring context using #SpyBean annotation.
Real methods of that class will be called during the test and you can verify them if needed similarly to the beans annotated with #MockBean
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class MyControllerTest {
#SpyBean
private MyValidator validator
}

Spring HATEOAS Resource assembler is not instantiated in unit test

I am trying to write a unit test for a REST controller which generates HATEOAS links via Resource assembler class. Everything is OK in production, but with the unit test Resource assembler class is not being injected into the controller.
my resource assembler class is:
#Component
public class ModelResourceAssembler extends ResourceAssemblerSupport<Model, ModelResource> {
public ModelResourceAssembler() {
super(ModelRestController.class, ModelResource.class);
}
#Bean
public ModelResourceAssembler modelResourceAssembler(){
return new ModelResourceAssembler();
}
#Override
public ModelResource toResource(Model model) {
...
}
}
The controller is:
#Controller
#RequestMapping("/demo")
#ComponentScan(basePackages = {"com.foo.demo"} )
public class ModelRestController {
#Autowired
private ModelPersistenceHandler modelPersistenceHandler;
#Autowired
private ModelResourceAssembler modelResourceAssembler;
...
}
And the unit test:
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes= {ModelResourceAssembler.class, ModelRestController.class})
public class ModelRestControllerTest {
private MockMvc mockMvc;
#InjectMocks
private ModelRestController modelRestController;
#Mock
private ModelPersistenceHandler modelPersistenceHandler;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(modelRestController).build();
}
...
}
No matter what I do the ModelResourceAssembler instance is always null. Since the application is Spring Boot it does not have the WebCoonfig classes and autowired WebApplicationContext is always null, so I cannot (and really don't want to since I am running a unit test) instantiate MockMvc via webAppContextSetup
The solution ended up being quite simple: I needed to add one line to my test:
#Spy
private ModelResourceAssembler modelResourceAssembler;
And the bean was instantiated and properly wired
In your example you use #InjectMocks but don't declare a mock for ModelResourceAssembler. You don't get an instance out of nowhere.
You use the MockitoJUnitRunner.class. It has no idea of Spring beans. For testing Spring applications you rather want to use SpringJUnit4ClassRunner.class.
If i may suggest, if you use constructor injection for your controller then you can just mock the dependency and not need spring junit test runner stuff.

Resources