Hi I have a Spring Boot (2.1.6 RELEASE) application, and I am trying add some simple integration test to my app.
Firstly I've create a base IntegrationTest class like below:
#TypeChecked
#Transactional
#Rollback
#SpringBootTest(classes = AppRunner.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
abstract class IntegrationTest extends Specification {
#Autowired
protected WebApplicationContext webApplicationContext
#Autowired
ObjectMapper objectMapper
MockMvc mockMvc
#Before
void setupMockMvc() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.build()
}
protected ResultActions makePost(final String uri, final Object dto) {
mockMvc.perform(post(uri)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(dtoToJson(dto))
)
}
private String dtoToJson(final Object dto) {
return objectMapper.writeValueAsString(dto)
}
}
This is my test method:
class TestForTest extends IntegrationTest {
#WithMockUser(username = "xxx#gmail.com")
def "should reset password and send mail with proper activation link"() {
given:
def email = "user2#xxx.com"
when:
ResultActions result = makePost("/rest/user/resendActivationMail", email)
then:
1 == 1
println(result)
}
}
Is strange because I have feeling the context was not be loaded, and so more there was no any try to do this.
I got error:
java.lang.IllegalArgumentException: WebApplicationContext is required
at org.springframework.util.Assert.notNull(Assert.java:198)
at org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder.<init>(DefaultMockMvcBuilder.java:52)
at org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup(MockMvcBuilders.java:51)
at pl.isolution.veolia.mveolia.spec.IntegrationTest.setupMockMvc(IntegrationTest.groovy:37)
Is there is a problem with Maven or IntellijIDEA? This took abot 340 ms, and I didn't see any attempt of context loading.
My stack:
JAVA 11, Maven 3, Spring Boot 2.1.6, Spock 1.3-groovy-2.5
Any suggestions?
Ok What a shame :) I missed a dependency.
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.3-groovy-2.5</version>
<scope>test</scope>
</dependency>
Related
I have a spring-boot Webflux application and I am writing some tests using Spock and Groovy.
My Controllers are secured with OAuth opaque token which I need to mock a response from introspection.
My test properties are:
spring.security.oauth2.resourceserver.opaquetoken.client-id=fake_client
spring.security.oauth2.resourceserver.opaquetoken.client-secret=fake_secret
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=http://localhost:8089/api/v1/oauth/token/introspect
My test uses WebClient as below:
webClient.post()
.uri(URL.toString()))
.accept(MediaType.APPLICATION_JSON)
.headers(http -> http.setBearerAuth("bearer_token"))
.exchange()
.expectStatus()
.is2xxSuccessful()
I found the solution.
You have to configure WireMockServer and then stub the response. Working solution below:
#ContextConfiguration
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SomeTest extends Specification {
private static final int PORT = 8089;
private WireMockServer wireMockServer;
def setup() {
wireMockServer = new WireMockServer(options().port(PORT))
wireMockServer.start()
WireMock.configureFor("localhost", wireMockServer.port())
def stubIntrospection() {
stubFor(post("/api/v1/oauth/token/introspect")
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.withBodyFile("path-to-my-file.json")))
}
def "my test case" () {
stubIntrospection()
//my test here
}
}
There is no difference between Spock and JUnit when mocking spring security-context. You can provide with mocked Authentication for both unit (#WebFluxTest) and integration (#SpringBootTest) with either:
org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOpaqueToken mutator for WebTestClient
#WithMockBearerTokenAuthentication annotation.
Mocked BearerTokenAuthentication, Spring's auth implementation for token introspection (unless you explicitly changed it in your conf), is put in test security-context directly by annotation or mutator: authorization header is not introspected (it is completely ignored and can be omitted).
Sample (with JUnit, just adapt to your test framework) in this project:
#Test
void securedRouteWithAuthorizedPersonnelViaMutatorIsOk() throws Exception {
api
.mutateWith(mockOpaqueToken()
.attributes(attributes -> attributes.put(StandardClaimNames.PREFERRED_USERNAME, "Ch4mpy"))
.authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
.get().uri("https://localhost/secured-route")
.exchange()
.expectStatus().isOk();
}
#Test
#WithMockBearerTokenAuthentication(
authorities = "ROLE_AUTHORIZED_PERSONNEL",
attributes = #OpenIdClaims(preferredUsername = "Ch4mpy"))
void securedRouteWithAuthorizedPersonnelViaAnnotationIsOk() throws Exception {
api
.get().uri("https://localhost/secured-route")
.exchange()
.expectStatus().isOk();
}
When testing other secured #Component type than #Controller (like #Service or #Repository), only test annotation is usable as there is no HTTP request:
#Import({ SecurityConfig.class, SecretRepo.class })
class SecretRepoTest {
// auto-wire tested component
#Autowired
SecretRepo secretRepo;
#Test
void whenNotAuthenticatedThenThrows() {
// call tested components methods directly (do not use MockMvc nor
// WebTestClient)
assertThrows(Exception.class, () -> secretRepo.findSecretByUsername("ch4mpy").block());
}
#Test
#WithMockBearerTokenAuthentication(attributes = #OpenIdClaims(preferredUsername = "Tonton Pirate"))
void whenAuthenticatedAsSomeoneElseThenThrows() {
assertThrows(Exception.class, () -> secretRepo.findSecretByUsername("ch4mpy").block());
}
#Test
#WithMockBearerTokenAuthentication(attributes = #OpenIdClaims(preferredUsername = "ch4mpy"))
void whenAuthenticatedWithSameUsernameThenReturns() {
assertEquals("Don't ever tell it", secretRepo.findSecretByUsername("ch4mpy").block());
}
}
Annotation is available from
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-oauth2-test</artifactId>
<scope>test</scope>
</dependency>
Which is a transient dependency of
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-webflux-introspecting-test</artifactId>
<scope>test</scope>
</dependency>
I used the later in sample to also have WebTestClientSupport, but first is enough for just test annotations.
My boss asked me to build some test on our Spring Boot application.
I'm asking myself which type of test should I implement, considering that the goal is to push the app on Jenkins that supports auto Testing.
Code based Test
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class ConfigTagControllerTestReal {
#Autowired
MockMvc mockMvc;
#Test
public void shouldReturnTheTagName() throws Exception{
this.mockMvc.perform(get("/configtag/librerie"))
.andDo(print())
.andExpect(status().isOk());
}
Completely Mock Test
#RunWith(SpringRunner.class)
#WebMvcTest(value = ConfigTagController.class , secure = false)
public class ConfigTagControllerTestMocked {
#Autowired
private MockMvc mockMvc;
#MockBean
private ConfigTagService service;
#Test
public void shouldFindTheTagName() throws Exception{
Collection<String> mockPaths= new ArrayList<>();
ConfigTagEntity mockTag = new ConfigTagEntity("culo",mockPaths);
Mockito.when(service.getByName(Mockito.anyString())).thenReturn(mockTag);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/configtag/culo")
.accept(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
}
I'm very new in testing and i need to understand the working method
Is there any tool or maven plugin that can validate Spring context during compile time or maven build execution?
I understand that it's not trivial to check the full correctness of the context without the app start, but it will be good to check some trivial cases, for example if you define a bean in xml context then the bean class have to be present in classpath.
Every Spring Guide contains such sanity test.
For Spring MVC should test with MockMvc test. To verify that Spring configuration is OK, you can Create full context and fire requests against URL and cover also validation + all the Spring wiring. Such test is executed during test maven phase.
Something like this:
#WebAppConfiguration
#ContextConfiguration(classes = RestApplication.class)
public class RestApplicationContextTest extends
AbstractTestNGSpringContextTests {
private static final String FULL_USER_URL = "http://localhost:10403/users";
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeMethod
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
private static String createTestRecord(int identifier) {
String testingRecordString =
"{\"email\": \"user%d#gmail.com\", \"name\": \"User%d\"}";
return String.format(testingRecordString, identifier, identifier,
identifier);
}
#Test
public void testPost() throws Exception {
// GIVEN
String testingRecord = createTestRecord(0);
// WHEN
// #formatter:off
MvcResult mvcResult = mockMvc.perform(post(FULL_USER_URL)
.contentType(MediaType.APPLICATION_JSON)
.content(testingRecord))
.andReturn();
// #formatter:on
// THEN
int httpStatus = mvcResult.getResponse().getStatus();
assertEquals(httpStatus, HttpStatus.CREATED.value());
}
...
I'm using Spring Boot 1.2.5-RELEASE. I have a controller that receive a MultipartFile and a String
#RestController
#RequestMapping("file-upload")
public class MyRESTController {
#Autowired
private AService aService;
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void fileUpload(
#RequestParam(value = "file", required = true) final MultipartFile file,
#RequestParam(value = "something", required = true) final String something) {
aService.doSomethingOnDBWith(file, value);
}
}
Now, the service works well. I tested it with PostMan and eveything goes as expected.
Unfortunately, I cannot write a standalone unit test for that code. The current unit test is:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#WebAppConfiguration
public class ControllerTest{
MockMvc mockMvc;
#Mock
AService aService;
#InjectMocks
MyRESTController controller;
#Before public void setUp(){
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void testFileUpload() throws Exception{
final File file = getFileFromResource(fileName);
//File is correctly loaded
final MockMultipartFile multipartFile = new MockMultipartFile("aMultiPartFile.txt", new FileInputStream(file));
doNothing().when(aService).doSomethingOnDBWith(any(MultipartFile.class), any(String.class));
mockMvc.perform(
post("/file-upload")
.requestAttr("file", multipartFile.getBytes())
.requestAttr("something", ":(")
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE))
.andExpect(status().isCreated());
}
}
Test fails with
java.lang.IllegalArgumentException: Expected MultipartHttpServletRequest: is a MultipartResolver configured?
Now, in the MultipartAutoConfiguration class from Spring Boot I see that a MultipartResolver is auto configured. But, I guess that with the standaloneSetup of MockMvcBuilders I cannot access this.
I tried several configurations of the unit test that I don't report for brevity. Especially, I also tried rest-assured as shown here, but honestly this doesn't work because it seems that I cannot mock the AService instance.
Any solution?
You are trying to combine here unit test (standaloneSetup(controller).build();) with Spring integration test (#RunWith(SpringJUnit4ClassRunner.class)).
Do one or the other.
Integration test will need to use something like code below. The problem would be faking of beans. There are ways to fake such bean with #Primary annotation and #Profile annotation (you create testing bean which will override main production bean). I have some examples of such faking of Spring beans (e.g. this bean is replaced by this bean in this test).
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeMethod
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
Secodn option is to remove #RunWith(SpringJUnit4ClassRunner.class) and other class level configuration on your test and test controller without Spring Context with standalone setup. That way you can't test validation annotations on your controller, but you can use Spring MVC annotations. Advantage is possibility to fake beans via Mockito (e.g. via InjectMocks and Mock annotations)
I mixed what lkrnak suggested and Mockito #Spy functionality. I use REST-Assured to do the call. So, I did as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#WebAppConfiguration
#IntegrationTest({"server.port:0"})
public class ControllerTest{
{
System.setProperty("spring.profiles.active", "unit-test");
}
#Autowired
#Spy
AService aService;
#Autowired
#InjectMocks
MyRESTController controller;
#Value("${local.server.port}")
int port;
#Before public void setUp(){
RestAssured.port = port;
MockitoAnnotations.initMocks(this);
}
#Test
public void testFileUpload() throws Exception{
final File file = getFileFromResource(fileName);
doNothing().when(aService)
.doSomethingOnDBWith(any(MultipartFile.class), any(String.class));
given()
.multiPart("file", file)
.multiPart("something", ":(")
.when().post("/file-upload")
.then().(HttpStatus.CREATED.value());
}
}
the service is defined as
#Profile("unit-test")
#Primary
#Service
public class MockAService implements AService {
//empty methods implementation
}
The error says the request is not a multi-part request. In other words at that point it's expected to have been parsed. However in a MockMvc test there is no actual request. It's just mock request and response. So you'll need to use perform.fileUpload(...) in order to set up a mock file upload request.
I have set up spring boot application using Gradle. Now I do understand that #EnableAutoConnfiguration configures the application based on dependencies in a class path. I am pretty happy to avoid all of the plumbing but things start happening which I wish wouldn't.
Here are my dependencies:
dependencies {
compile('org.springframework.boot:spring-boot-starter-web:1.2.3.RELEASE')
compile 'org.springframework.hateoas:spring-hateoas:0.17.0.RELEASE'
compile 'org.springframework.plugin:spring-plugin-core:1.2.0.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-data-jpa'
compile 'com.google.guava:guava:18.0'
compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
compile 'commons-beanutils:commons-beanutils:1.9.2'
runtime 'org.hsqldb:hsqldb:2.3.2'
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'com.jayway.jsonpath:json-path:2.0.0'
}
My application class:
#ComponentScan("org.home.project")
#SpringBootApplication
//#EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
A snippet from UserController:
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<ResourceSupport> create(#Valid #RequestBody UserCreateRequest ucr, BindingResult bindingResult) {
if (bindingResult.hasErrors()) throw new InvalidRequestException("Bad Request", bindingResult);
Long userId = userService.create(ucr);
ResourceSupport resource = new ResourceSupport();
resource.add(linkTo(UserEndpoint.class).withSelfRel());
resource.add(linkTo(methodOn(UserEndpoint.class).update(userId, null, null)).withRel(VIEW_USER));
resource.add(linkTo(methodOn(UserEndpoint.class).delete(userId)).withRel(DELETE_USER));
return new ResponseEntity(resource, HttpStatus.CREATED);
}
The UserController.java has two annotations:
#RestController
#RequestMapping(value = "/users", produces = MediaType.APPLICATION_JSON_VALUE)
First of - notice the commented out #EnableHyperdiaSupport annotation - links in the ResourceSupport instance are still serialized to hal+json format despite media type produced and media type set in the request. This happens automatically when 'org.springframework.plugin:spring-plugin-core:1.2.0.RELEASE' is introduced in the dependencies. How would one go about configuring it explicitly ?
Another issue are unit tests.
This passes:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MockServletContext.class)
#WebAppConfiguration
public class UserControllerTest {
...ommited for brevity...
#InjectMocks
private UserController testObject;
#Before
public void setUp() throws Exception {
initMocks(this);
mockMvc = standaloneSetup(testObject).build();
}
#Test
public void testUserCreatedLinks() throws Exception {
mockMvc.perform(post("/users").contentType(MediaType.APPLICATION_JSON).content(data))
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(jsonPath("$.links.[*].rel", hasItem("self")));
}
...ommited fro brevity...
}
The post request returns a standard JSON response in the test - not HAL+JSON. Is there a way to reconfigure this so that unit testing #RestController with MockServletContext would produce HAL+JSON or getting back to problem number 1 - how to configure the response format explicitly so that Jackson serializer would not produce hal+json ?
You're running your test using Spring MVC Test's standaloneSetup which uses a bare minimum of configuration to get your controller up and running. That configuration isn't the same as the configuration that will be used when you run the whole application.
If you want to use the same configuration, you could use webAppContextSetup:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class SomeTests {
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
}
Alternatively, you can replicate Spring HATEOAS's configuration in the standalone setup. Note that this runs the risk of your tests' configuration deviating from your application's configuration. You'd create the MockMvc instance like this:
TypeConstrainedMappingJackson2HttpMessageConverter messageConverter =
new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
messageConverter.setSupportedMediaTypes(Arrays.asList(MediaTypes.HAL_JSON));
ObjectMapper objectMapper = messageConverter.getObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
objectMapper.setHandlerInstantiator(
new Jackson2HalModule.HalHandlerInstantiator(new DefaultRelProvider(), null));
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(testObject)
.setMessageConverters(messageConverter).build();