#WebMvcTest needs #Import(SecurityConfig.class) to enable SpringSecurity - spring-boot

First of all, its important to specify that the app is already deployed and our spring security configuration works. If an unauthenticated user tries to access to any endpoint of the API it returns 401 Unauthorized.
Following, this sample example, I want to test a Controller with #WebMvcTest and spring security
#WebMvcTest(EmployeeController.class)
#Import(SecurityConfig.class)
class EmployeeControllerTest {
#Autowired
private WebApplicationContext ctx;
protected MockMvc mvc;
#MockBean
private EmployeeService service;
#BeforeEach
public void setUp() {
this.mvc = MockMvcBuilders
.webAppContextSetup(ctx)
.apply(springSecurity())
.build();
}
#Test
void unauthorized_role_should_return_401() {
mvc.perform(get("/employees/1").accept(MediaType.APPLICATION_JSON)
.with(SecurityMockMvcRequestPostProcessors.user("manager").roles("UNKNOWN")))
.andExpect(status().isUnauthorized())
}
}
This code works, but I don't understand why I need to import the SecurityConfig class to the test class. Indeed, if I remove the #Import, mockMvc returns 200. But, every sample project that I found over Github simply use #WebMvcTest even if the project has a SecurityConfig class

There is no need to configure extra thing in order to enable spring security when using #WebMvcTest as #WebMvcTest will enable it automatically.
But if you customise the spring-security by creating your own beans , you still have to define these customised beans for the tests for the customisation to work. #Import is one of the way to define these beans. By consolidating their configuration into a SecurityConfig , it can keep your test cases DRY because if a test case need to test with the security stuff , it just need to import this SecurityConfig rather repeatedly do the same configuration for each test.
So it is rather a code design decision which is an application specific and hence it is normal that not every project will have the same kind of setup.

Related

how to correctly modularize app configuration, so that tests(IT,datajpa,...) does not pick up everything intended for production

In our app I found out, that my integration tests picks up more stuff than I'd like. I'd like to know, how correctly structured app configuration looks like, what do you use, so that I can #Import in tests only those configuration which are used in production, which are needed.
I believe relevant page in documentation is:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-user-configuration
... it's stressed there, that it's important to structure code in sensible way, however it's not shown that much, what that is/means. I know about profiles and can probably create profile which would be unmatched in tests and import manually, but that's probably not that sensible way they were talking about.
Consider this main entrypoint:
#SpringBootApplication
public class DemoApplication {
private final SomeService someService;
public DemoApplication(SomeService someService) {
this.someService = someService;
}
#EventListener(ApplicationReadyEvent.class)
public void started() {
System.out.println(someService.doIt());
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
interface of some service:
public interface SomeService {
public String doIt();
}
and configuration:
#Configuration
public class Config {
#Bean
public SomeService createSomeServiceBean() {
return new SomeService() {
#Override
public String doIt() {
return String.format("Hi! (At %s)", LocalDateTime.now());
}
};
}
}
When invoked, entrypoint annotated by #SpringBootApplication will do component scan, will discover configuration and it will work. Reading further in documentation we will find sentence: Test slices exclude #Configuration classes from scanning([if #ComponentScan does have default value of basePackages and basePackagesClasses]), however following test:
#SpringBootTest
class DemoApplicationTests {
#Autowired
private SomeService someService;
#Test
void contextLoads() {
System.out.println(someService.doIt());
}
}
just happily discovers SomeService bean defined. Or did that sentence meant just that tests annotated by for example #DataJpaTest won't register some configurations? Kinda unclear to me, but it does not seem possible, since how would #DataJpaTest would know, which configurations to ommit and which not.
Again, I know how to use profiles/excluding configurations. I'm asking about "sensible way of structuring app".
How to sensibly structure you app and how to configure it so that:
#SpringBootApplication annotated entrypoint will do component scan, find and use configurations, for production, but these configurations needs to be manually imported in tests?
some packages will be automatically scanned for configurations which will be used both in development and tests environments.
The Spring Boot Test support provides annotations that allow to only create a Spring Context with the relevant beans to testing a specific slice of your application.
There is no specific package structure or naming strategy required to make use of this feature.
Here are some of these:
#DataJpaTest: You get a Spring Context with relevant beans to test your JPA <-> Database interface: EntityManager, DataSource, all your interfaces extending JpaRepository
#WebMvcTest: You get a Spring Context with a mocked servlet environment for testing your web layer that includes the following beans for your: all your controller, controller advice, WebMvcConfigurer , Filter, etc. but not anything that is annotated with e.g. #Service or #Component
#SpringBootTest: This will give you a full Spring Context and tries to create all beans for you. You can exclude some autoconfiguration e.g. if you don't want autoconfiguration to kick in:
Example:
#SpringBootTest(webEnvironment = RANDOM_PORT)
#TestPropertySource(properties=
{"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration"})
There are way more test slice annotations, you can have a look at here
So these annotations are smart in a way that they know which beans they should include in the context and which to exclude.
A general approach to testing your application can be to use the first two test annotations stated above to verify web and data layer in isolation. Next use Mockito and plain JUnit 5 to unit test your service classes. And finally, write some integration test that creates the whole Spring Context with #SpringBootTest to test everything together.

Integration testing with spring declarative caching

I'm trying to write integration tests for a Spring Boot 2 Application.
One test should test updating a value via REST.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureTestEntityManager
#Transactional
public class TenantEndpointIT {
#Autowired
private TestRestTemplate template;
#Autowired
private TestEntityManager entityManager;
#Test
public void nok_updateValueForbidden() {
}
}
Now, I thought the cleanest way was to create the value with the TestEntityManager in the #Before method, then test the REST endpoint in the actual test.
But the service called by the REST Endpoint is annotated with Spring Caching annotations. So the test fails if I do that. I could use the service directly or make a second REST call. That creates problems with other tests using the same Value, because even if the DB is rolled-back, the cache seems to contain the value. (Now I'm using #DirtiesContext).
My question is, how do you correctly integration test services with #Cachable?
Is there a way to get the Cache and explicitly put/remove?
I tried autowiring a CacheManager, but it won't find one and fails.
If you add #AutoConfigureCache on your test, it will override whatever cache strategies you've defined in your app by a CacheManager that noops. That's pretty useful if you want to make sure that cache doesn't interfere with your tests.

Unable to find a SpringBootConfiguration in Spring Boot Test 1.4

I'm not able to run a simple test in spring boot 1.4. I followed the tutorial from the official site testing-the-spring-mvc-slice but I didn't get it to work.
every time i get the following error:
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test
any ideas, hints?
Thanks in advance
Edit:
this is the controller
#Controller
public class UserManagementController {
#GetMapping(value = "/gs/users/getUsers")
public #ResponseBody String getAllUsers() {
return "test";
}
}
this is the test
#RunWith(SpringRunner.class)
#WebMvcTest(UserManagementController.class)
public class UserManagementControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void showUserView() throws Exception {
this.mvc.perform(get("/gs/users/getUsers"))
.andExpect(status().isOk())
.andDo(print());
}
}
From my point of view it's exactly the same like this post from the site.
the #WebMvcTest will do:
Auto-configure Spring MVC, Jackson, Gson, Message converters etc.
Load relevant components (#Controller, #RestController, #JsonComponent etc)
Configure MockMVC
now why i need to configure a "super" class
The search algorithm works up from the package that contains the test
until it finds a #SpringBootApplication or #SpringBootConfiguration
annotated class. As long as you’ve structure your code in a sensible
way your main configuration is usually found.
So you have annotated your test with #*Test. It run, checked for configuration in subclasses, haven't found any, thrown an exception.
You have to have a config in a package or subpackage of test class or directly pass config class to #ContextConfiguration or #SpringBootTest or have class annotated with #SpringBootApplication.
According to #SpringBootApplication. I have tested controller in way you have mentioned with #WebMvcTest: it works if application has class annotated as #SpringBootApplication and fails with exception you've mentioned if not. There is remark it the article you mentioned:
In this example, we’ve omitted classes which means that the test will
first attempt to load #Configuration from any inner-classes, and if
that fails, it will search for your primary #SpringBootApplication
class.
Github discussion about the same point.
Spring Boot Documentation

Spring Annotation #WebMvcTest does not work in an app that has Jpa repositories

I have a Spring App that uses JPA repositories (CrudRepository interfaces). When I try to test my controller using the new Spring test syntax #WebMvcTest(MyController.class), it fails coz it tries to instantiate one of my service class that uses JPA Repository, does anyone has any clues on how to fix that? The app works when I run it.
Here is the error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.myapp.service.UserServiceImpl required a bean of type 'com.myapp.repository.UserRepository' that could not be found.
Action:
Consider defining a bean of type 'com.myapp.repository.UserRepository' in your configuration.
According to the doc
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. #Controller, #ControllerAdvice, #JsonComponent Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not #Component, #Service or #Repository beans).
This annotion only apply on the Spring MVC components.
If you are looking to load your full application configuration and use MockMVC, you should consider #SpringBootTest combined with #AutoConfigureMockMvc rather than this annotation.
I was able to unit test a Rest Controller by implementing junit 5 and using #SpringJUnitConfig along with #WebMvcTest. I am using Spring Boot 2.4.5 and this is my example:
#SpringJUnitConfig
#WebMvcTest(controllers = OrderController.class)
class OrderControllerTest {
#Autowired
private MockMvc mockMvc;
// This is a Mock bean of a Spring Feign client that calls an external Rest Api
#MockBean
private LoginServiceClient loginServiceClient;
// This is a Mock for a class which has several Spring Jpa repositories classes as dependencies
#MockBean
private OrderService orderService;
#DisplayName("should create an order")
#Test
void createOrder() throws Exception {
OrderEntity createdOrder = new OrderEntity("123")
when(orderService.createOrder(any(Order.class))).thenReturn(createdOrder);
mockMvc.perform(post("/api/v1/orders").contentType(MediaType.APPLICATION_JSON).content("{orderId:123}"))
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))TODO: here it will go the correlationId
.andExpect(jsonPath("$.orderId").value("123"));
}
}
Please only use #SpringBootTest when you are implementing integration tests.
I faced this same problem. Using #SpringBootTest and #AutoConfigureMockMvc worked perfectly for me.

How to ensure load time weaving takes place for Eclipselink when using SpringBootTest with other tests running beforethe Spring one

I'm using Spring Rest Docs to generate documentation for my REST services. This involves running unit(strictly integration) tests that run against a live Spring Boot Container that is kicked off by the test. The test class looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MySpringConfiguration.class)
#WebAppConfiguration
public class ApiDocumentation {
private MockMvc mockMvc;
#Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("target/generated-snippets");
#Autowired
private WebApplicationContext context;
#Autowired
private ObjectMapper objectMapper;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
}
#Test
public void testSomething() throws Exception {
}
}
The application uses JPA with EclipseLink for the EntityManager implementation.
When I run the test standalone in my IDE or as the only test present when I run a Maven build using the maven-surefire-plugin everything works fine.
However it's not the only test I want to run in the suite. As soon as I run other tests in the suite I come across the issue mentioned here, namely
"Spring's agent does not initialize the persistence context until the application accesses the Spring context. If the application has already triggered the loading of the persistent class before accessing the Spring context, weaving will not occur."
and get errors like this:
Exception Description: The method [_persistence_set_someField_vh] or [_persistence_get_someField_vh] is not defined in the object [mypackage.MyEntity].
So what do people normally do to get around this ? Run SpringBootTest classes in a different module to unit tests that access entities ?
As far as I concerned problem caused by dynamic weaving, if you make it static it should work proper. Possibly it could help you
Another solution could be to disable dynamic weaving in that particular test using eclipselink.weaving JPA property.
See this question and its answers: #SpringBootTest interferes with EclipseLink dynamic weaving

Resources