I am using cucumber tests to test my spring boot app with spring security enabled .Things work fine except when I run my test suite with cucumber tests some tests using spring security eg.
#WithMockUser(username = "BROWSER", roles =
{"BROWSER","ADMIN"})
fail .These tests do work if I do run them in seclusion as simple junit tests but fail when run with cucumber test steps.
The issue looks like the spring security test mock behaviour isnt getting applied when I run the same with cucumber tests.
My cucumber test run class is as below
#RunWith(Cucumber.class)
#CucumberOptions(features = "src/test/resources", monochrome = true, format =
{"pretty", "html:src/main/resources/static/cucumber"})
public class CucumberTests
{
}
Also I noticed the same works when run via Maven with <reuseForks>false</reuseForks> .Also maven triggered test case run also fails if this option is not checked .
UPDATE
AbstractIntegrationTest class all tests extend
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Services.class,loader = SpringApplicationContextLoader.class)
//#IntegrationTest
#WebIntegrationTest(randomPort = true)
public abstract class AbstractIntegrationTest {
Another use case which does not work is using theses annotation is cucumber feature conditions like below
#When("^I apply a GET on (.*)$")
#WithMockUser(username = "BROWSER", roles = { "BROWSER", "ADMIN" })
public void i_search_with_rsql(String query) throws Throwable {
result = mvc.perform(get(uri, query));
}
any help or workaround on this.
WithMockUser does not work with Cucumber. Use cucumber hooks instead.
WithMockUser relies on TestExecutionListener#beforeTestMethod from Spring's test context support, but they are not invoked when running with Cucumber runner. This is because Cucumber runs scenarios composed of steps rather than the standard JUnit test methods.
Option 1 - Security context hooks. You can setup security context with hooks, for example:
#ActiveProfiles("test")
#SpringBootTest(classes = MyServer.class)
#AutoConfigureMockMvc
#AutoConfigureRestDocs
#AutoConfigureCache
public class MyServerContextHooks {
#Before
public void onBeforeScenario(final Scenario scenario) {
// This method does nothing - context setup is done with annotations
}
}
Example annotation on scenarios:
#WithAdminUser
Scenario: Run action as admin
...
Example hook to use annotation on scenarios:
public class TestUserHooks {
#Before("#WithAdminUser")
public void setupAdminUser() {
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(
"admin",
"N/A",
createAuthorityList("admin")));
}
}
Option 2 - Authentication steps. Another way is to use special steps for providing user into mockMvc:
Scenario: Run action as admin
Given I am logged in as admin
...
Stepdef example:
public class SecurityTestSteps {
#Autowired
private MockMvcGlue mockMvc;
#Autowired
private OAuth2Mocks oauth2Mocks;
#Autowired
private TestUsers users;
/**
* Provides a one of predefined role-based authentications for the current request.
*/
#Given("^I am logged in as (admin|editor|user)$")
public void given_UserIsAuthenticatedWithRole(final String role) {
switch (role) {
case "admin":
mockMvc.request().with(authentication(oauth2Mocks.auth(users.admin())));
break;
case "editor":
mockMvc.request().with(authentication(oauth2Mocks.auth(users.edtior())));
break;
default:
throw new CucumberException("Unsupported role <" + role + ">");
}
}
}
Base upon your comments you need to ensure to apply Spring Security. You can find an example of this in the Setting Up MockMvc and Spring Security section of the reference documentation:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
// ...
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity()) // ADD THIS!
.build();
}
In Response to Rob Winch's answer, mine worked using his method minus the line
".apply(springSecurity())"
Related
I'm writing some integration tests for my Spring MVC Controller.
The controllers are secured by Spring Security.
This is the test class I currently have:
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.MOCK,
classes = GuiBackendApplication.class
)
#AutoConfigureMockMvc
public class ConfigEditorControllerIntegrationTest {
#Autowired
private MockMvc mockedMvc;
#Test
#WithMockUser(username = "user", password = "password", roles = {"admin"})
public void adminsCanAccessRuntimeConfig() throws Exception {
this.mockedMvc.perform(get("/my/custom/api"))
.andExpect(status().isOk());
}
}
This test class ensures that admins can access my endpoint. It works fine.
BUT what if I want to test if ONLY users with the admin role can access my endpoint?
I could write a test that uses #WithMockUsers with all the roles I currently have except the admin role. But that would me awful to maintain. I want my test to ensure that only users with the admin role can access my endpoint, regardless of any new roles.
I checked the Spring Reference Docs and didn't find anything about that. Is there a way to achieve that?
Something like this
#Test
#WithMockUser(username = "user", password = "password", roles = {"IS NOT admin"})
public void nonAdminsCannotAccessRuntimeConfig() throws Exception {
this.mockedMvc.perform(get("/my/custom/api"))
.andExpect(status().isUnauthorized());
}
Spring Security does not know what roles does your system define. So you have to tell it and test it one by one if you want to have 100% test coverage for all the available roles.
You can do it easily and in a maintenance way by using JUnit 5 's #ParameterizedTest and configuring MockMvc with the UserRequestPostProcessor with different roles.
Something like :
public class ConfigEditorControllerIntegrationTest {
#ParameterizedTest
#MethodSource
public void nonAdminsCannotAccessRuntimeConfig(String role) throws Exception {
mockedMvc.perform(get("/my/custom/api")
.with(user("someUser").roles(role)))
.andExpect(status().isUnauthorized());
}
static List<String> nonAdminsCannotAccessRuntimeConfig() {
return Roles.exclude("admin");
}
}
And create a class to maintain all the available roles :
public class Roles {
public static List<String> all() {
return List.of("admin", "hr", "developer" , "accountant" , .... , "devops");
}
public static List<String> exclude(String excludeRole) {
List<String> result = new ArrayList<>(all());
result.remove(excludeRole);
return result;
}
}
I am following instructions in https://www.docs4dev.com/javadoc/en/org/springframework/boot/spring-boot-test/2.2.2.RELEASE/org/springframework/boot/test/system/OutputCaptureRule.html
I am using Maven with spring-boot-starter-parent version 2.2.2.
My test is very simple:
#SpringBootTest(classes = MyApplication.class) // this loads Springboot context
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
#ContextConfiguration(classes = {MyTestConfig.class, AnotherTestConfig.class})
public class MyTest {
#Rule
public OutputCaptureRule output = new OutputCaptureRule();
#Test
public void theTest() {
assertThat(output).contains("something");
}
}
However, when I put a breakpoint in the assert line and try to evaluate output.getOut(), the result is:
Method threw 'java.lang.IllegalStateException' exception. The details message is:
No system captures found. Please check your output capture registration.
It seems like the feature is not working out of the box. Any idea what I am missing?
Most probably it is because you are using JUnit 5 as SpringBoot 2.2 provides JUnit 5 by default , but the OutputCaptureRule is the JUnit 4 's TestRule stuff and hence it cannot be activated under JUnit 5.
You should use the equivalent OutputCaptureExtension in JUnit 5 instead :
#SpringBootTest(classes = MyApplication.class)
#ExtendWith(OutputCaptureExtension.class)
public class MyTest {
#Test
public void theTest(CapturedOutput output) {
assertThat(output).contains("something");
}
}
I have to create a integration test for a microservice X which downloads, processes and importing csv files from external sftp servers. The whole process is started by a spring boot scheduler task which starts a spring batch job for processing and importing the data. The import process is done by the spring batch writer, which is a restTemplate Repository (so it calls post requests to another microservice Y).
I already managed to mock the sftp server, putting a test file on it and the current integration test is downloading the file. (https://github.com/stefanbirkner/fake-sftp-server-rule/)
My problem is, that the task will be scheduled immediately when the application context starts so there is no trigger like a api call. To get the whole integration test working i have to mock the part where the external microservice Y is called through a restTemplate call. This repository is called in the spring batch writer and this repository is created by a repositoryFactory which is a #Service. The repositoryFactory is injected in the spring batch configuration class.
I already tried to use the #MockBean annotation in the test class as well as in a separate test configuration where i am mocking the create() function of the factory to deliver a repository mock. But at some point it does not work and it delivers still the original object which leads to interupt the import job.
I also tried to use the WireMock library, but also in this case it does not catched any api calls and at some point leads to interrupt the sftp socket. (?)
I hope someone could help me out.
The current test:
#NoArgsConstructor
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(classes = {JsonHalConfig.class})
#TestPropertySource(locations = "classpath:application-test.properties")
#TestMethodOrder(MethodOrderer.MethodName.class)
public class ImportIT {
#ClassRule
public static final FakeSftpServerRule sftpServer = new FakeSftpServerRule();
private static final String PASSWORD = "password";
private static final String USER = "username";
private static final int PORT = 14022;
#BeforeClass
public static void beforeClass() throws IOException {
URL resource = getTestResource();
if (resource != null) {
sftpServer.setPort(PORT).addUser(USER, PASSWORD);
sftpServer.createDirectories("/home/username/it-space/IMPORT", "/home/username/it-space/EXPORT");
sftpServer.putFile("/home/username/it-space/IMPORT/INBOX/testFile.csv",
resource.openStream());
} else {
throw new IOException("Failed to get test resources");
}
}
private static URL getTestResource() {
return ImportIT.class.getClassLoader().getResource("testFile.csv");
}
#Test
public void test_A_() throws IOException, RepositoryException {
assertTrue(true);
}
}
I tried following configuration classes
(included in #ContextConfiguration)
#Configuration/#TestConfiguration
public class RepositoryTestConfig {
#Bean
#Primary
public IRepositoryFactory repositoryFactory() {
IRepositoryFactory repositoryFactory = mock(IRepositoryFactory.class);
IRepository repository = mock(IRepository.class);
when(repositoryFactory.create(anyString())).thenReturn(repository);
return repositoryFactory;
}
}
(as static class in the test class)
#TestConfiguration/#Configuration
public static class RepositoryTestConfig {
#MockBean
private IRepositoryFactory repositoryFactory;
#PostConstruct
public void initMock(){
IRepository repository = mock(IRepository.class);
Mockito.when(repositoryFactory.create(anyString())).thenReturn(
repository
);
}
}
UPDATE 27.08.2021
I have a RestConfig #Component where a new RestTemplateBuilder is created. I tried to #MockBean this component to deliver a RestTemplateBuilder Mock and injected a MockRestServiceServer object to catch outgoing api calls. But unfortunately it does not work as aspected. Am i missing something? I also tried to create a "TestRestController" to trigger the scheduling of the task but it never delivers the mock...
I normally use #MockBean directly inside my test classes and inject the dedicated (but mocked) repository directly there and not create it inside the test configuration. I also add the test configuration class by #ContextConfiguration so it is loaded in current test context.
Inside my tests I am just using mockito the standard way and prepare the mocked parts as wanted for the dedicated test method.
Here an example snippet:
// ... do some imports ...
#RunWith(SpringRunner.class)
#ContextConfiguration(classes= {XYZSomeWantedClazz.class, DemoXYZMockTest.SimpleTestConfiguration.class})
#ActiveProfiles({Profiles.TEST})
public class DemoXYZMockTest {
//...
#MockBean
private DemoRepository mockedDemoRepository;
// ...
#Test
public void testMethodName() throws Exception{
/* prepare */
List<WantedEntityClazz> list = new ArrayList<>();
// add your wanted data to your list
// apply to mockito:
when(mockedDemoRepository.findAll()).thenReturn(list);
/* execute */
// ... execute the part you want to test...
/* test */
// ... test the results after execution ...
}
#TestConfiguration
#Profile(Profiles.TEST)
#EnableAutoConfiguration
public static class SimpleTestConfiguration{
// .. do stuff if necessary or just keep it empty
}
}
For a complete (old Junit4) working test example please take a look at:
https://github.com/mercedes-benz/sechub/blob/3f176a8f4c00b7e8577c9e3bea847ecfc91974c3/sechub-administration/src/test/java/com/daimler/sechub/domain/administration/signup/SignupAdministrationRestControllerMockTest.java
I am learning Junit 5 and test cases.
I am using spring boot version '2.2.6.RELEASE and JUnit 5,
in my application, I have a method that processes based on the boolean flag from property file.
\src\main\resources\application.properties
#data base connection properties
spring.app.datasource.url=jdbc:mysql://localhost:3306/student_db
spring.app.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.app.datasource.username=root
spring.datasource.password=root
spring.app.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
#additional properties
spring.property.name=shrikant
spring.property.enable=false
database connection properties are used to create the database connection
Datasource.java
#Value("${spring.app.datasource.url}")
private String url;
#Value("${spring.app.datasource.driver-class-name}")
private String className;
#Value("${spring.app.datasource.username}")
private String userName;
#Value("${spring.datasource.password}")
private String password;
#Value("${spring.app.jpa.properties.hibernate.dialect}")
private String dialect;
controller class
#RestController
public class Controller {
#Value("${spring.property.name}")
private String name;
#Value("${spring.property.enable}")
private boolean status;
public void validateObject(String surName) {
if (status) { # if this flag is true then only process
System.out.println("name= " + name);
System.out.println("surName= " + surName);
}
}
ControllerTest.java
#SpringBootTest
class ControllerTest {
#Autowired
private Controller controller;
#Test
void show() {
controller.validateObject("sharma");
}
by default the flag is false, so every time test case runs it never processes the object.
so I tried to create aplication.properties in the test folder
\src\test\resources\application.properties
spring.property.name=vishal
spring.property.enable=true
but now it's giving me an error that
Could not resolve placeholder 'spring.app.datasource.url'
but I don't want to provide DB connection URL, I am not connecting to the database while testing.
Q1 - how to change the value of properties file for test case only.
Q2 - is it mandatory to provide all the keys of \src\main\resources\application.properties is \src\test\resources\application.properties?
I am new in test case, so little explained answers would be welcomed.
Update:-
I found that
#SpringBootTest
#TestPropertySource(properties = {"spring.property.name=vishal", " spring.property.status=true"})
class ControllerTest {
will solve the issue temporarily, by providing keys along with values, but I have a lot of keys, which cannot be provided in such a way.
If you use #SpringBootTest then your test will load the whole Spring context. This means it will create all your beans and try to wire them together. If you inject property values to your beans, you have to specify them all for such tests as otherwise, you won't be able to boot the application.
What might help you in such a situation is to use test annotations like #WebMvcTest or #DataJpaTest to focus on testing just slices of your application. Using #WebMvcTest you'll get an application context just containing controllers and everything related to your web layer. Other beans (like service classes) can be mocked with #MockedBean.
Next, for testing business logic in service classes try not to use #SpringBootTest and rather rely on plain JUnit 5 and Mockito to verify your code.
You still might want to have some integration tests that use #SpringBootTest to make sure everything is working together. In such case, you can hard code any static property inside application.properties in src/test/resources/ or using the annotation like you already did: #TestPropertySource(properties = {"spring.property.name=vishal", " spring.property.status=true"}).
When it comes to providing a database, you can either configure an embedded database (which I would try to avoid) or use the same database as in production. Testcontainers helps you a lot when it comes to providing external infrastructure for your tests like a database.
An example setup with Spring Boot >= 2.2.6 and JUnit might look like the following:
#Testcontainers
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationIT {
#Container
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer()
.withPassword("password")
.withUsername("username");
#DynamicPropertySource
static void postgresqlProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
}
#Test
public void contextLoads() {
}
}
For Junit5, annotate your test class with path to application.properties in your src/main/resources:
#ExtendWith(SpringExtension.class)
#TestPropertySource("classpath:application.properties")
public class MyTest {
#Value("${property.name}")
private int myProperty;
tests...
}
property gets loaded
I implemented resilience4j in my project using the Spring Boot2 starter (https://resilience4j.readme.io/docs/getting-started-3).
I annotated a method with #CircuitBreaker that uses http client for calling an external service and the circuit breaker is working fine - including its fallback.
I'd like to add unit tests for it but when I run a test trying to simulate the fallback, nothing happens - the exception is thrown but is not handled by the circuit breaker mechanism.
I've found some examples using its metrics but it is not useful in my case.
Any thoughts?
Here is a snippet of my client:
#CircuitBreaker(name = "MY_CICUIT_BREAKER", fallbackMethod = "fallback")
public ResponseEntity<String> search(String value) {
ResponseEntity<String> responseEntity = restTemplate.exchange(
searchURL,
HttpMethod.GET,
new HttpEntity(new HttpHeaders()),
String.class,
value);
}
public ResponseEntity<String> fallback(String value, ResourceAccessException ex) {
return "fallback executed";
}
As andres and pvpkiran mentioned/explained, I had to add a integration test.
You can achieve that basically adding #SpringBootTest annotation to your test class, it will bootstrap a container with spring context on it.
I also autowired CircuitBreakerRegistry in order to reset the circuit breaker before each test so I could guarantee a clean test. For mocking/spying/verifying I used Mockito from spring boot test starter (spring-boot-starter-test).
Here is how I managed to test the fallbacks methods:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = Application.class)
public class RestClientIntegrationTest {
private final String SEARCH_VALUE = "1234567890";
#MockBean( name = "myRealRestTemplateName")
private RestTemplate restTemplate;
#SpyBean
private MyRestClient client;
#Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;
#BeforeEach
public void setUp() {
circuitBreakerRegistry.circuitBreaker("MY_CIRCUIT_BREAKER_NAME").reset();
}
#Test
public void should_search_and_fallback_when_ResourceAccessException_is_thrown() {
// prepare
when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class), eq(SEARCH_VALUE)))
.thenThrow(ResourceAccessException.class);
String expectedResult = "expected result when fallback is called";
// action
String actualResult = client.search(SEARCH_VALUE);
// assertion
verify(client).fallback(eq(SEARCH_VALUE), any(ResourceAccessException.class));
assertThat(actualResult, is(expectedResult));
}
}
I hope there is no compilation error since I had to remove some non-relevant stuff.
You shouldn't test #CircuitBreaker in a unit test as it involves more than one class. Rather use an integration test.