Spring Boot 2.1/2.2 testing - how to test a single controller without creating beans for everything else? - spring

Before in Spring Boot 2.0 I had something like:
#RunWith(SpringRunner::class)
#DataJpaTest
#AutoConfigureMockMvc
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ActiveProfiles("unit-test")
#SpringBootTest
#WithUserDetails
class MyControllerTest {
#InjectMocks
lateinit var myController: MyController
lateinit var mvc: MockMvc
#Before
fun setup() {
mvc = MockMvcBuilders.standaloneSetup(myController).build()
}
...
But after trying to upgrade to Spring Boot 2.1, I get various random errors, such as:
WithUserDetails not working: java.lang.IllegalStateException: Unable to create SecurityContext using #org.springframework.security.test.context.support.WithUserDetails(value=user, userDetailsServiceBeanName=, setupBefore=TEST_METHOD)
Irrelevant beans were (attempting) to be created: kotlin.UninitializedPropertyAccessException: lateinit property <property> has not been initialized - this was from a #ConfigurationProperties class.
and a few more other stuff that just didn't make sense to me (in 2.2, I can't have both #DataJpaTest and #SpringBootTest together either).
Does anyone have an idea for what I would need to do to correctly update these unit tests?

You use either a sliced test #WebMvcTest or a full integration test with #SpringBootTest. Hence it has no sense in using them together. In your case you want to test a controller, then use the #WebMvcTest and mock the dependencies.
#RunWith(SpringRunner::class)
#WebMvcTest(MyController.class)
#WithUserDetails
class MyControllerTest {
#Autowired
lateinit var myController: MyController
#Autowired
lateinit var mvc: MockMvc
#MockBean
var myService: MyServiceForController
Use #MockBean to mock the service dependencies for the controller and register behavior on it. You can now also simply wire the controller and a pre-setup MockMvc instance instead of wiring that yourself.

Related

PostConstruct and test

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.

MockBean in a Spring boot test not getting autowired into the component being tested

I have a spring boot Service using an autowired ApplicationEventPublisher:
#Service("ILiveOrderHandlerImpl")
#NoArgsConstructor
#RequiredArgsConstructor
public class ILiveOrderHandlerImpl implements ILiveOrderHandler {
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
In the spring boot integration test I have specified #MockBean for ApplicationEventPublisher.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {StockTraderApplication.class},
class ILiveOrderHandlerImplTest {
#Autowired
#Qualifier("ILiveOrderHandlerImpl")
private ILiveOrderHandler liveOrderHandler;
#MockBean
ApplicationEventPublisher mockApplicationEventPublisher;
While debugging I find that the field isn't mocked but it is the real object (org.springframework.web.context.support.GenericWebApplicationContext). In the test, the field is the mocked object that fails "verify()" test because it was never autowired. Why does this happen?

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 .

Use Mockito to register spring bean when doing mockMvc testing

Here is my idea, I try to test my Restful controller with MockMvc
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(post(...).param(..))
.andExpect(...);
The API I call fires a rabbitmq message. But I don't want to test Amqp in the test. Instead, I create a mock producer like this:
#Mock
private AmqpProducer producer
I want to inject this producer into the spring context, so I can capture the method call producer.sendMessage and test the message.
To mock a bean in the Spring Boot contexte you cannot use directly #Mock.
It will create a mock for AmqpProducer but not which used by your container.
With Spring, the classical way to do that is annotating your test class with a specific context configuration class or file (#ContextConfiguration(...)) that provides the mock.
With Spring Boot, it is simpler : annotating your class with #WebMvcTest
and your field to mock with #MockBean is enough to mock the bean in the container (Spring guide).
Note that #WebMvcTest with a specified controller class specified in the annotation value will instantiate the specified controller and also all its direct dependencies declared. So you should mock all of them and not only which one that interests you in your unit test.
So it should look like :
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class WebMockTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private AmqpProducer producer;
#Test
public void foo() throws Exception {
this.mockMvc.perform(...);
verify(producer).sendMessage(expected);
}
}

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)

Resources