Spring5 + Rest In Agijle (1)
Agile is used widely during developer’s daily life. Although it was abused a lot to hold unnecessary daily meeting to update status. But it is definitely not the reason why Agile is used at initial beginning.
Per personal experience, I just apply the agile and tdd in our daily work even at the initial step of project as coder.
Create Spring5 With Java 11
The first step is just go https://start.spring.io/ and create the baseline of spring5 project.E
The generated code will like like import the project into IntelliJ Idea
The most basic idea of agile in coding way is to have each step of small and correct. There is no big jump or surprise in the whole SDLC.
At first, make sure the our first step is passed.
Run “./mvnw clean install”
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 24.337 s
[INFO] Finished at: 2018-12-14T17:11:00-05:00
[INFO] ------------------------------------------------------------------------
Now we hit the first step of agile, make project works.
Create DemoController
package org.lz.boilerplate.springrest.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("api/demo")
public class DemoController {
public static final String HELLOWORLD = "helloworld";
@GetMapping("hello")
public String hello(){
return HELLOWORLD;
}
}
Create UnitTest Code
We write out the firs unit test to make sure the our code still work.
The test code will start the tomcat with random port and call it.
We can either use mockmvc to test the controller directly without web server started.
But this paper will focus more about the whole code infrastructure build up, testing with full sever will be better way.
package org.lz.boilerplate.springrest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringRestApplicationTests {
public static final String DEMO_HELLO_API = "/api/demo/hello";
public static final String DEMO_HELLO_API_EXPECTED = "helloworld";
@Autowired
private TestRestTemplate restTemplate;
@Test
public void callHelloAPIOk() {
String body = this.restTemplate.getForObject(DEMO_HELLO_API, String.class);
Assert.assertEquals(DEMO_HELLO_API_EXPECTED, body);
}
}
Run with “./mvnw test”
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.749 s - in
org.lz.boilerplate.springrest.SpringRestApplicationTests
2018-12-14 23:11:29.919 INFO 46526 --- [ Thread-2] o.s.s.concurrent.ThreadPoolTaskExecutor :
Shutting down ExecutorService 'applicationTaskExecutor'
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
We are still at good status.
Now I don’t like the test name, I will refactor the name to “DemoRestApiTests” to self introduce itself.
Introduce the Spring Security OAuth
We will start the from test case as the first step 2 have test cases
@Test
public void callHelloApiFailIfNoCredential() {
ResponseEntity<String> resp = this.restTemplate
.exchange(API_DEMO_HELLO, HttpMethod.GET, null, String.class );
Assert.assertEquals(HttpStatus.UNAUTHORIZED, resp.getStatusCode());
}
@Test
public void callHelloApiOk() {
ResponseEntity<String> resp = this.restTemplate.withBasicAuth("user", "password")
.exchange(API_DEMO_HELLO, HttpMethod.GET, null, String.class );
Assert.assertEquals(HttpStatus.OK, resp.getStatusCode());
Assert.assertEquals(HELLO_API_EXPECTED, resp.getBody());
}
And we got failed test case.
The first package we added should be package below:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Create WebSecurityConfig per Spring5’s reference docs:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("{noop}password").roles("ADMIN").and()
.withUser("user").password("{noop}password").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
Or in the more generic way, we create a UserDetailsService for customized data access.
Like
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ensure the passwords are encoded properly
User.UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
Finally, we still pass all test cases. It means our code are still safe to checkin in day 1.
Next day let’s do it together to add JWT. The idea will be still same and simple: test for each tiny step to make sure our code are workable and reliable.
Comments
Post a Comment