├── src ├── main │ ├── resources │ │ ├── db-compatibility-mode.sql │ │ ├── db-test-data.sql │ │ ├── db-schema.sql │ │ ├── logback.xml │ │ ├── security-context.xml │ │ └── application-context.xml │ ├── java │ │ └── com │ │ │ └── jayway │ │ │ ├── domain │ │ │ └── Account.java │ │ │ ├── repository │ │ │ ├── AccountRepository.java │ │ │ └── AccountEntity.java │ │ │ ├── config │ │ │ ├── WebConfig.java │ │ │ ├── ApplicationConfig.java │ │ │ ├── RepositoryConfig.java │ │ │ ├── SpringBootRunner.java │ │ │ ├── SecurityConfig.java │ │ │ ├── JndiRepositoryConfig.java │ │ │ ├── MySqlRepositoryConfig.java │ │ │ └── H2RepositoryConfig.java │ │ │ ├── controller │ │ │ ├── Amount.java │ │ │ ├── Transfer.java │ │ │ └── BankController.java │ │ │ ├── service │ │ │ ├── UnknownAccountException.java │ │ │ ├── AccountService.java │ │ │ ├── ImmutableAccount.java │ │ │ └── AccountServiceImpl.java │ │ │ └── security │ │ │ └── SecureAccountServiceImpl.java │ └── webapp │ │ └── WEB-INF │ │ └── web.xml └── test │ ├── java │ └── com │ │ └── jayway │ │ ├── repository │ │ ├── AccountEntityTest.java │ │ ├── EmbeddedDbJavaConfigTest.java │ │ ├── EmbeddedDbXmlConfigTest.java │ │ ├── AccountRepositoryTest.java │ │ ├── EmbeddedDbJavaConfig.java │ │ └── AccountEntityTransactionalTest.java │ │ ├── config │ │ ├── H2RepositoryConfigTest.java │ │ └── MySqlRepositoryConfigIT.java │ │ ├── controller │ │ ├── AmountTest.java │ │ ├── BankControllerBasicTest.java │ │ ├── BankControllerTransactionalTest.java │ │ ├── TransferTest.java │ │ └── BankControllerMvcTest.java │ │ ├── application │ │ ├── RestAssuredSecureBankApplicationIT.java │ │ ├── RestTemplateSecureBankApplicationIT.java │ │ ├── BankApplicationTest.java │ │ ├── RestAssuredBankApplicationIT.java │ │ └── RestTemplateBankApplicationIT.java │ │ ├── service │ │ ├── AccountServiceImplMockitoTest.java │ │ └── AccountServiceImplTest.java │ │ └── security │ │ └── SecureAccountServiceImplTest.java │ └── resources │ └── embedded-db-application-context.xml ├── .gitignore ├── README.md └── pom.xml /src/main/resources/db-compatibility-mode.sql: -------------------------------------------------------------------------------- 1 | SET MODE MySQL; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ IDEA 2 | *.iml 3 | *.ipr 4 | *.iws 5 | *.ids 6 | .idea/ 7 | 8 | # Maven 9 | target/ 10 | 11 | # OS X 12 | .DS_Store -------------------------------------------------------------------------------- /src/main/resources/db-test-data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO account_t (account_number, balance) VALUES (1, 100); 2 | INSERT INTO account_t (account_number, balance) VALUES (2, 200); -------------------------------------------------------------------------------- /src/main/java/com/jayway/domain/Account.java: -------------------------------------------------------------------------------- 1 | package com.jayway.domain; 2 | 3 | 4 | public interface Account { 5 | 6 | Integer getAccountNumber(); 7 | 8 | int getBalance(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/db-schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS account_t; 2 | 3 | CREATE TABLE account_t ( 4 | account_number INT NOT NULL AUTO_INCREMENT, 5 | balance INT NOT NULL, 6 | PRIMARY KEY (account_number), 7 | CHECK (balance >= 0) 8 | ); -------------------------------------------------------------------------------- /src/main/java/com/jayway/repository/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package com.jayway.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | @Repository 7 | public interface AccountRepository extends JpaRepository { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %5p: %m%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package com.jayway.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 7 | 8 | @Configuration 9 | @ComponentScan("com.jayway.controller") 10 | @EnableWebMvc 11 | public class WebConfig extends WebMvcConfigurerAdapter {} 12 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/controller/Amount.java: -------------------------------------------------------------------------------- 1 | package com.jayway.controller; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import javax.validation.constraints.Min; 7 | 8 | class Amount { 9 | 10 | @Min(message = "Amount must be >= 0", value = 0) 11 | private final int amount; 12 | 13 | @JsonCreator 14 | Amount(@JsonProperty("amount") int amount) { 15 | this.amount = amount; 16 | } 17 | 18 | int getAmount() { 19 | return amount; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/config/ApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package com.jayway.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Import; 6 | import org.springframework.transaction.annotation.EnableTransactionManagement; 7 | 8 | @Configuration 9 | @ComponentScan("com.jayway.service") 10 | @EnableTransactionManagement 11 | @Import({H2RepositoryConfig.class, MySqlRepositoryConfig.class, JndiRepositoryConfig.class}) 12 | public class ApplicationConfig {} 13 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/config/RepositoryConfig.java: -------------------------------------------------------------------------------- 1 | package com.jayway.config; 2 | 3 | import org.springframework.beans.factory.FactoryBean; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.transaction.PlatformTransactionManager; 6 | 7 | import javax.persistence.EntityManagerFactory; 8 | import javax.sql.DataSource; 9 | 10 | public interface RepositoryConfig { 11 | 12 | @Bean 13 | DataSource dataSource(); 14 | 15 | @Bean 16 | FactoryBean entityManagerFactory(); 17 | 18 | @Bean 19 | PlatformTransactionManager transactionManager(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/config/SpringBootRunner.java: -------------------------------------------------------------------------------- 1 | package com.jayway.config; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Import; 7 | 8 | 9 | @Configuration 10 | @EnableAutoConfiguration 11 | @Import({ApplicationConfig.class, WebConfig.class, SecurityConfig.class}) 12 | public class SpringBootRunner { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(SpringBootRunner.class, args); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/main/java/com/jayway/service/UnknownAccountException.java: -------------------------------------------------------------------------------- 1 | package com.jayway.service; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class UnknownAccountException extends RuntimeException { 8 | 9 | private static final long serialVersionUID = -5562010974917511663L; 10 | 11 | public UnknownAccountException(Integer accountNumber) { 12 | super("Unknown account number: " + accountNumber); 13 | } 14 | 15 | public UnknownAccountException(Integer accountNumber, Exception cause) { 16 | super("Unknown account number: " + accountNumber, cause); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/service/AccountService.java: -------------------------------------------------------------------------------- 1 | package com.jayway.service; 2 | 3 | import java.util.List; 4 | 5 | public interface AccountService { 6 | 7 | 8 | ImmutableAccount get(Integer accountNumber) throws UnknownAccountException; 9 | 10 | 11 | void deposit(Integer accountNumber, int amount) throws UnknownAccountException; 12 | 13 | 14 | ImmutableAccount withdraw(Integer accountNumber, int amount) throws UnknownAccountException; 15 | 16 | 17 | void transfer(Integer fromAccountNumber, Integer toAccountNumber, int amount) throws UnknownAccountException; 18 | 19 | 20 | Integer createAccount(); 21 | 22 | 23 | void deleteAccount(Integer accountNumber) throws UnknownAccountException; 24 | 25 | 26 | List getAllAccountNumbers(); 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/repository/AccountEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.repository; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class AccountEntityTest { 10 | 11 | 12 | AccountEntity accountEntity; 13 | 14 | 15 | @Before 16 | public void setUp() { 17 | accountEntity = new AccountEntity(); 18 | } 19 | 20 | 21 | @Test 22 | public void initialBalanceShouldBeZero() { 23 | assertThat(accountEntity.getBalance(), is(0)); 24 | } 25 | 26 | 27 | @Test 28 | public void shouldDeposit() { 29 | accountEntity.deposit(10); 30 | assertThat(accountEntity.getBalance(), is(10)); 31 | } 32 | 33 | 34 | @Test 35 | public void shouldWithdraw() { 36 | accountEntity.withdraw(10); 37 | assertThat(accountEntity.getBalance(), is(-10)); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/controller/Transfer.java: -------------------------------------------------------------------------------- 1 | package com.jayway.controller; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | 5 | import javax.validation.constraints.Min; 6 | import javax.validation.constraints.NotNull; 7 | import java.util.Map; 8 | 9 | class Transfer { 10 | 11 | @NotNull(message = "From account number must be set") 12 | private Integer fromAccountNumber; 13 | 14 | @NotNull(message = "To account number must be set") 15 | private Integer toAccountNumber; 16 | 17 | @Min(message = "Amount must be >= 0", value = 0) 18 | private int amount; 19 | 20 | Transfer(Integer fromAccountNumber, Integer toAccountNumber, int amount) { 21 | this.fromAccountNumber = fromAccountNumber; 22 | this.toAccountNumber = toAccountNumber; 23 | this.amount = amount; 24 | } 25 | 26 | @JsonCreator 27 | Transfer(Map map) { 28 | this(map.get("fromAccountNumber"), map.get("toAccountNumber"), map.get("amount")); 29 | } 30 | 31 | Integer getFromAccountNumber() { 32 | return fromAccountNumber; 33 | } 34 | 35 | Integer getToAccountNumber() { 36 | return toAccountNumber; 37 | } 38 | 39 | int getAmount() { 40 | return amount; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/config/H2RepositoryConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.config; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.test.context.ActiveProfiles; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 | import org.springframework.transaction.PlatformTransactionManager; 10 | 11 | import javax.persistence.EntityManagerFactory; 12 | import javax.sql.DataSource; 13 | 14 | import static org.junit.Assert.assertNotNull; 15 | 16 | @RunWith(SpringJUnit4ClassRunner.class) 17 | @ContextConfiguration(classes = H2RepositoryConfig.class) 18 | @ActiveProfiles("h2") 19 | public class H2RepositoryConfigTest { 20 | 21 | @Autowired 22 | DataSource dataSource; 23 | 24 | @Autowired 25 | EntityManagerFactory entityManagerFactory; 26 | 27 | @Autowired 28 | PlatformTransactionManager transactionManager; 29 | 30 | @Test 31 | public void createsDataSource() { 32 | assertNotNull(dataSource); 33 | } 34 | 35 | @Test 36 | public void createsEntityManagerFactory() { 37 | assertNotNull(entityManagerFactory); 38 | } 39 | 40 | @Test 41 | public void createsTransactionManager() { 42 | assertNotNull(transactionManager); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/config/MySqlRepositoryConfigIT.java: -------------------------------------------------------------------------------- 1 | package com.jayway.config; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.test.context.ActiveProfiles; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 | import org.springframework.transaction.PlatformTransactionManager; 10 | 11 | import javax.persistence.EntityManagerFactory; 12 | import javax.sql.DataSource; 13 | 14 | import static org.junit.Assert.assertNotNull; 15 | 16 | @RunWith(SpringJUnit4ClassRunner.class) 17 | @ContextConfiguration(classes = MySqlRepositoryConfig.class) 18 | @ActiveProfiles("mysql") 19 | public class MySqlRepositoryConfigIT { 20 | 21 | @Autowired 22 | DataSource dataSource; 23 | 24 | @Autowired 25 | EntityManagerFactory entityManagerFactory; 26 | 27 | @Autowired 28 | PlatformTransactionManager transactionManager; 29 | 30 | @Test 31 | public void createsDataSource() { 32 | assertNotNull(dataSource); 33 | } 34 | 35 | @Test 36 | public void createsEntityManagerFactory() { 37 | assertNotNull(entityManagerFactory); 38 | } 39 | 40 | @Test 41 | public void createsTransactionManager() { 42 | assertNotNull(transactionManager); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/controller/AmountTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.controller; 2 | 3 | import org.junit.BeforeClass; 4 | import org.junit.Test; 5 | 6 | import javax.validation.ConstraintViolation; 7 | import javax.validation.Validation; 8 | import javax.validation.Validator; 9 | import javax.validation.ValidatorFactory; 10 | import java.util.Set; 11 | 12 | import static org.hamcrest.CoreMatchers.is; 13 | import static org.junit.Assert.assertThat; 14 | 15 | public class AmountTest { 16 | 17 | static Validator validator; 18 | 19 | Amount amount; 20 | 21 | 22 | @BeforeClass 23 | public static void beforeClass() { 24 | ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); 25 | validator = factory.getValidator(); 26 | } 27 | 28 | 29 | @Test 30 | public void shouldAllowZeroAmount() { 31 | amount = new Amount(0); 32 | 33 | Set> constraintViolations = 34 | validator.validate(amount); 35 | 36 | assertThat(constraintViolations.size(), is(0)); 37 | } 38 | 39 | 40 | @Test 41 | public void shouldNotAllowNegativeAmount() { 42 | amount = new Amount(-1); 43 | 44 | Set> constraintViolations = 45 | validator.validate(amount); 46 | 47 | assertThat(constraintViolations.size(), is(1)); 48 | assertThat(constraintViolations.iterator().next().getMessage(), 49 | is("Amount must be >= 0")); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/controller/BankControllerBasicTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.controller; 2 | 3 | import com.jayway.service.AccountService; 4 | import com.jayway.service.ImmutableAccount; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.InjectMocks; 8 | import org.mockito.Mock; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import static org.hamcrest.CoreMatchers.is; 12 | import static org.junit.Assert.assertThat; 13 | import static org.mockito.Mockito.verify; 14 | import static org.mockito.Mockito.when; 15 | 16 | @RunWith(MockitoJUnitRunner.class) 17 | public class BankControllerBasicTest { 18 | 19 | @Mock 20 | AccountService accountServiceMock; 21 | 22 | @Mock 23 | ImmutableAccount immutableAccountMock; 24 | 25 | @InjectMocks 26 | BankController bankController; 27 | 28 | 29 | @Test 30 | public void shouldGetAccount() { 31 | when(accountServiceMock.get(1)).thenReturn(immutableAccountMock); 32 | 33 | ImmutableAccount account = bankController.get(1); 34 | 35 | assertThat(account, is(immutableAccountMock)); 36 | } 37 | 38 | 39 | @Test 40 | public void shouldDepositToAccount() { 41 | bankController.deposit(1, new Amount(50)); 42 | 43 | verify(accountServiceMock).deposit(1, 50); 44 | } 45 | 46 | 47 | @Test 48 | public void shouldDeleteAccount() { 49 | bankController.delete(1); 50 | 51 | verify(accountServiceMock).deleteAccount(1); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/resources/security-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/service/ImmutableAccount.java: -------------------------------------------------------------------------------- 1 | package com.jayway.service; 2 | 3 | import com.jayway.domain.Account; 4 | 5 | public class ImmutableAccount implements Account { 6 | 7 | private final Integer accountNumber; 8 | 9 | private final int balance; 10 | 11 | 12 | public ImmutableAccount(Integer accountNumber, int balance) { 13 | this.accountNumber = accountNumber; 14 | this.balance = balance; 15 | } 16 | 17 | 18 | @Override 19 | public Integer getAccountNumber() { 20 | return accountNumber; 21 | } 22 | 23 | 24 | @Override 25 | public int getBalance() { 26 | return balance; 27 | } 28 | 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) return true; 33 | if (!(o instanceof ImmutableAccount)) return false; 34 | 35 | ImmutableAccount that = (ImmutableAccount) o; 36 | 37 | if (balance != that.balance) return false; 38 | if (accountNumber != null ? !accountNumber.equals(that.accountNumber) : that.accountNumber != null) 39 | return false; 40 | 41 | return true; 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | int result = accountNumber != null ? accountNumber.hashCode() : 0; 47 | result = 31 * result + balance; 48 | return result; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "ImmutableAccount{" + 54 | "accountNumber=" + accountNumber + 55 | ", balance=" + balance + 56 | '}'; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.jayway.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.config.http.SessionCreationPolicy; 13 | 14 | @Configuration 15 | @ComponentScan("com.jayway.security") 16 | @EnableWebSecurity 17 | @EnableGlobalMethodSecurity(prePostEnabled = true) 18 | @Profile("!disableSecurity") 19 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 20 | 21 | @Override 22 | protected void configure(HttpSecurity http) throws Exception { 23 | http 24 | .authorizeRequests().anyRequest().hasAuthority("ACCOUNT_OWNER").and() 25 | .csrf().disable() 26 | .httpBasic().and() 27 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); 28 | } 29 | 30 | @Autowired 31 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 32 | auth 33 | .inMemoryAuthentication() 34 | .withUser("user") 35 | .password("secret") 36 | .authorities("ACCOUNT_OWNER"); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/application/RestAssuredSecureBankApplicationIT.java: -------------------------------------------------------------------------------- 1 | package com.jayway.application; 2 | 3 | import com.jayway.config.SpringBootRunner; 4 | import org.apache.http.HttpStatus; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.boot.test.IntegrationTest; 8 | import org.springframework.boot.test.SpringApplicationConfiguration; 9 | import org.springframework.test.context.ActiveProfiles; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | import org.springframework.test.context.web.WebAppConfiguration; 12 | 13 | import static com.jayway.restassured.RestAssured.given; 14 | import static com.jayway.restassured.RestAssured.when; 15 | 16 | @RunWith(SpringJUnit4ClassRunner.class) 17 | @ActiveProfiles("mysql") 18 | @WebAppConfiguration 19 | @IntegrationTest 20 | @SpringApplicationConfiguration(classes = SpringBootRunner.class) 21 | public class RestAssuredSecureBankApplicationIT { 22 | 23 | 24 | @Test 25 | public void shouldReturnOkIfProvidingGoodCredentials() { 26 | given(). 27 | auth(). 28 | basic("user", "secret"). 29 | when(). 30 | get("/accounts/1"). 31 | then(). 32 | statusCode(HttpStatus.SC_OK); 33 | } 34 | 35 | 36 | @Test 37 | public void shouldNotGetAccountIfNotProvidingCredentials() { 38 | when(). 39 | get("/accounts/1"). 40 | then(). 41 | statusCode(HttpStatus.SC_UNAUTHORIZED); 42 | } 43 | 44 | 45 | @Test 46 | public void shouldNotGetAccountIfProvidingBadCredentials() { 47 | given(). 48 | auth(). 49 | basic("unknown", "password"). 50 | when(). 51 | get("/accounts/1"). 52 | then(). 53 | statusCode(HttpStatus.SC_UNAUTHORIZED); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/repository/EmbeddedDbJavaConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.repository; 2 | 3 | 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.jdbc.core.JdbcTemplate; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | 12 | import javax.sql.DataSource; 13 | 14 | import static org.hamcrest.CoreMatchers.is; 15 | import static org.junit.Assert.assertThat; 16 | 17 | @RunWith(SpringJUnit4ClassRunner.class) 18 | @ContextConfiguration(classes = EmbeddedDbJavaConfig.class) 19 | public class EmbeddedDbJavaConfigTest { 20 | 21 | 22 | JdbcTemplate jdbcTemplate; 23 | 24 | @Autowired 25 | AccountRepository accountRepository; 26 | 27 | @Autowired 28 | DataSource dataSource; 29 | 30 | 31 | @Before 32 | public void setUp() { 33 | jdbcTemplate = new JdbcTemplate(dataSource); 34 | } 35 | 36 | 37 | int getBalance(int accountNumber) { 38 | return jdbcTemplate.queryForObject( 39 | "SELECT balance FROM account_t WHERE account_number = ?", 40 | Integer.class, accountNumber); 41 | } 42 | 43 | 44 | @Test 45 | public void verifyEmbeddedDatabase() { 46 | int firstBalance = getBalance(1); 47 | assertThat(firstBalance, is(100)); 48 | 49 | int secondBalance = getBalance(2); 50 | assertThat(secondBalance, is(200)); 51 | } 52 | 53 | 54 | @Test 55 | public void ormMappingShouldWork() { 56 | AccountEntity accountEntity = accountRepository.findOne(1); 57 | 58 | assertThat(accountEntity.getAccountNumber(), is(1)); 59 | assertThat(accountEntity.getBalance(), is(100)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/repository/EmbeddedDbXmlConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.repository; 2 | 3 | 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.jdbc.core.JdbcTemplate; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | 12 | import javax.sql.DataSource; 13 | 14 | import static org.hamcrest.CoreMatchers.is; 15 | import static org.junit.Assert.assertThat; 16 | 17 | @RunWith(SpringJUnit4ClassRunner.class) 18 | @ContextConfiguration("/embedded-db-application-context.xml") 19 | public class EmbeddedDbXmlConfigTest { 20 | 21 | 22 | JdbcTemplate jdbcTemplate; 23 | 24 | @Autowired 25 | AccountRepository accountRepository; 26 | 27 | @Autowired 28 | DataSource dataSource; 29 | 30 | 31 | @Before 32 | public void setUp() { 33 | jdbcTemplate = new JdbcTemplate(dataSource); 34 | } 35 | 36 | 37 | int getBalance(Integer accountNumber) { 38 | return jdbcTemplate.queryForObject( 39 | "SELECT balance FROM account_t WHERE account_number = ?", 40 | Integer.class, accountNumber); 41 | } 42 | 43 | 44 | @Test 45 | public void verifyEmbeddedDatabase() { 46 | int firstBalance = getBalance(1); 47 | assertThat(firstBalance, is(100)); 48 | 49 | int secondBalance = getBalance(2); 50 | assertThat(secondBalance, is(200)); 51 | } 52 | 53 | 54 | @Test 55 | public void ormMappingShouldWork() { 56 | AccountEntity accountEntity = accountRepository.findOne(1); 57 | 58 | assertThat(accountEntity.getAccountNumber(), is(1)); 59 | assertThat(accountEntity.getBalance(), is(100)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/repository/AccountEntity.java: -------------------------------------------------------------------------------- 1 | package com.jayway.repository; 2 | 3 | import com.jayway.domain.Account; 4 | 5 | import javax.persistence.*; 6 | import javax.validation.constraints.Min; 7 | 8 | @Entity 9 | @Table(name = "account_t") 10 | public class AccountEntity implements Account { 11 | 12 | @Id 13 | @Column(name = "account_number") 14 | @GeneratedValue(strategy = GenerationType.IDENTITY) 15 | private Integer accountNumber; 16 | 17 | @Column(name = "balance") 18 | @Min(message = "Balance must be >= 0", value = 0) 19 | private int balance; 20 | 21 | 22 | @Override 23 | public Integer getAccountNumber() { 24 | return accountNumber; 25 | } 26 | 27 | 28 | @Override 29 | public int getBalance() { 30 | return balance; 31 | } 32 | 33 | 34 | public void deposit(int amount) { 35 | balance += amount; 36 | } 37 | 38 | 39 | public void withdraw(int amount) { 40 | balance -= amount; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (!(o instanceof AccountEntity)) return false; 47 | 48 | AccountEntity that = (AccountEntity) o; 49 | 50 | if (balance != that.balance) return false; 51 | if (accountNumber != null ? !accountNumber.equals(that.accountNumber) : that.accountNumber != null) 52 | return false; 53 | 54 | return true; 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | int result = accountNumber != null ? accountNumber.hashCode() : 0; 60 | result = 31 * result + balance; 61 | return result; 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "AccountEntity{" + 67 | "accountNumber=" + accountNumber + 68 | ", balance=" + balance + 69 | '}'; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/repository/AccountRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.repository; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.test.context.ContextConfiguration; 7 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8 | import org.springframework.test.context.transaction.TransactionConfiguration; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import javax.persistence.EntityManager; 12 | import javax.persistence.PersistenceContext; 13 | 14 | import static org.hamcrest.CoreMatchers.is; 15 | import static org.hamcrest.CoreMatchers.nullValue; 16 | import static org.junit.Assert.assertThat; 17 | 18 | @RunWith(SpringJUnit4ClassRunner.class) 19 | @ContextConfiguration(classes = EmbeddedDbJavaConfig.class) 20 | // @ContextConfiguration("/embedded-db-application-context.xml") 21 | @Transactional 22 | @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true) 23 | public class AccountRepositoryTest { 24 | 25 | @PersistenceContext 26 | EntityManager entityManager; 27 | 28 | 29 | @Autowired 30 | AccountRepository accountRepository; 31 | 32 | 33 | @Test 34 | public void shouldGetAccountByNumber() { 35 | AccountEntity account = accountRepository.findOne(1); 36 | 37 | assertThat(account.getBalance(), is(100)); 38 | } 39 | 40 | 41 | @Test 42 | public void newAccountsShouldHaveZeroBalance() { 43 | AccountEntity account = accountRepository.save(new AccountEntity()); 44 | 45 | entityManager.flush(); 46 | 47 | assertThat(account.getBalance(), is(0)); 48 | } 49 | 50 | 51 | @Test 52 | public void canDeleteAccount() { 53 | accountRepository.delete(1); 54 | 55 | entityManager.flush(); 56 | 57 | assertThat(accountRepository.findOne(1), nullValue()); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/config/JndiRepositoryConfig.java: -------------------------------------------------------------------------------- 1 | package com.jayway.config; 2 | 3 | import org.springframework.beans.factory.FactoryBean; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.Profile; 8 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 9 | import org.springframework.jndi.JndiObjectFactoryBean; 10 | import org.springframework.orm.jpa.JpaTransactionManager; 11 | import org.springframework.transaction.PlatformTransactionManager; 12 | 13 | import javax.persistence.EntityManagerFactory; 14 | import javax.sql.DataSource; 15 | 16 | @Profile("prod") 17 | @Configuration 18 | @EnableJpaRepositories("com.jayway.repository") 19 | public class JndiRepositoryConfig implements RepositoryConfig { 20 | 21 | @Autowired 22 | private EntityManagerFactory entityManagerFactory; 23 | 24 | @Bean 25 | @Override 26 | public DataSource dataSource() { 27 | JndiObjectFactoryBean factoryBean = new JndiObjectFactoryBean(); 28 | factoryBean.setJndiName("java:comp/env/jdbc/myds"); 29 | factoryBean.setExpectedType(DataSource.class); 30 | return (DataSource) factoryBean.getObject(); 31 | } 32 | 33 | @Bean 34 | @Override 35 | public FactoryBean entityManagerFactory() { 36 | JndiObjectFactoryBean factoryBean = new JndiObjectFactoryBean(); 37 | factoryBean.setJndiName("persistence/bankDemo"); 38 | factoryBean.setExpectedType(EntityManagerFactory.class); 39 | return factoryBean; 40 | } 41 | 42 | @Bean 43 | @Override 44 | public PlatformTransactionManager transactionManager() { 45 | JpaTransactionManager transactionManager = new JpaTransactionManager(); 46 | transactionManager.setEntityManagerFactory(entityManagerFactory); 47 | return transactionManager; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/controller/BankControllerTransactionalTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.controller; 2 | 3 | 4 | import com.jayway.config.ApplicationConfig; 5 | import com.jayway.config.WebConfig; 6 | import com.jayway.service.ImmutableAccount; 7 | import com.jayway.service.UnknownAccountException; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.test.context.ActiveProfiles; 12 | import org.springframework.test.context.ContextConfiguration; 13 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 14 | import org.springframework.test.context.transaction.TransactionConfiguration; 15 | import org.springframework.test.context.web.WebAppConfiguration; 16 | import org.springframework.transaction.annotation.Transactional; 17 | 18 | import static org.hamcrest.CoreMatchers.is; 19 | import static org.junit.Assert.assertThat; 20 | 21 | @RunWith(SpringJUnit4ClassRunner.class) 22 | @ContextConfiguration(classes = {WebConfig.class, ApplicationConfig.class}) 23 | @ActiveProfiles({"h2"}) 24 | @TransactionConfiguration 25 | @Transactional 26 | @WebAppConfiguration 27 | public class BankControllerTransactionalTest { 28 | 29 | 30 | @Autowired 31 | BankController bankController; 32 | 33 | 34 | @Test 35 | public void shouldGetAccount() { 36 | ImmutableAccount immutableAccount = bankController.get(1); 37 | 38 | assertThat(immutableAccount.getBalance(), is(100)); 39 | } 40 | 41 | 42 | @Test 43 | public void shouldDepositToAccount() { 44 | bankController.deposit(1, new Amount(50)); 45 | 46 | ImmutableAccount immutableAccount = bankController.get(1); 47 | 48 | assertThat(immutableAccount.getBalance(), is(150)); 49 | } 50 | 51 | 52 | @Test(expected = UnknownAccountException.class) 53 | public void shouldDeleteAccount() { 54 | bankController.delete(1); 55 | 56 | bankController.get(1); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/service/AccountServiceImplMockitoTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.service; 2 | 3 | import com.jayway.repository.AccountEntity; 4 | import com.jayway.repository.AccountRepository; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.InjectMocks; 8 | import org.mockito.Mock; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import static org.mockito.Mockito.*; 12 | 13 | @RunWith(MockitoJUnitRunner.class) 14 | public class AccountServiceImplMockitoTest { 15 | 16 | @Mock 17 | AccountRepository accountRepositoryMock; 18 | 19 | @Mock 20 | AccountEntity accountEntityMock; 21 | 22 | @InjectMocks 23 | AccountServiceImpl accountServiceImpl; 24 | 25 | 26 | @Test 27 | public void shouldDepositToAccount() { 28 | when(accountRepositoryMock.findOne(1)).thenReturn(accountEntityMock); 29 | 30 | accountServiceImpl.deposit(1, 100); 31 | 32 | verify(accountEntityMock).deposit(100); 33 | } 34 | 35 | 36 | @Test 37 | public void shouldWithdrawFromAccount() { 38 | AccountEntity returnedAccountEntity = mock(AccountEntity.class); 39 | when(accountRepositoryMock.findOne(2)).thenReturn(accountEntityMock); 40 | when(accountRepositoryMock.save(accountEntityMock)).thenReturn(returnedAccountEntity); 41 | 42 | accountServiceImpl.withdraw(2, 200); 43 | 44 | verify(accountEntityMock).withdraw(200); 45 | } 46 | 47 | 48 | @Test 49 | public void shouldTransferBetweenAccount() { 50 | AccountEntity fromAccountMock = mock(AccountEntity.class); 51 | AccountEntity toAccountMock = mock(AccountEntity.class); 52 | 53 | when(accountRepositoryMock.findOne(1)).thenReturn(fromAccountMock); 54 | when(accountRepositoryMock.findOne(2)).thenReturn(toAccountMock); 55 | 56 | accountServiceImpl.transfer(1, 2, 500); 57 | 58 | verify(fromAccountMock).withdraw(500); 59 | verify(toAccountMock).deposit(500); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/test/resources/embedded-db-application-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | org.springframework.orm.hibernate4.SpringSessionContext 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | org.springframework.web.context.ContextLoaderListener 10 | 11 | 12 | 13 | contextClass 14 | org.springframework.web.context.support.AnnotationConfigWebApplicationContext 15 | 16 | 17 | 18 | contextConfigLocation 19 | com.jayway.config.ApplicationConfig,com.jayway.config.SecurityConfig 20 | 21 | 22 | 23 | spring.profiles.default 24 | prod 25 | 26 | 27 | 28 | springSecurityFilterChain 29 | org.springframework.web.filter.DelegatingFilterProxy 30 | 31 | 32 | 33 | springSecurityFilterChain 34 | /* 35 | 36 | 37 | 38 | bank 39 | org.springframework.web.servlet.DispatcherServlet 40 | 41 | contextClass 42 | org.springframework.web.context.support.AnnotationConfigWebApplicationContext 43 | 44 | 45 | contextConfigLocation 46 | com.jayway.config.WebConfig 47 | 48 | 1 49 | 50 | 51 | 52 | 53 | bank 54 | /* 55 | 56 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/application/RestTemplateSecureBankApplicationIT.java: -------------------------------------------------------------------------------- 1 | package com.jayway.application; 2 | 3 | import com.jayway.config.SpringBootRunner; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.boot.test.IntegrationTest; 7 | import org.springframework.boot.test.SpringApplicationConfiguration; 8 | import org.springframework.boot.test.TestRestTemplate; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.test.context.ActiveProfiles; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | import org.springframework.test.context.web.WebAppConfiguration; 14 | import org.springframework.web.client.RestTemplate; 15 | 16 | import java.util.Map; 17 | 18 | import static org.hamcrest.Matchers.is; 19 | import static org.junit.Assert.assertThat; 20 | 21 | @RunWith(SpringJUnit4ClassRunner.class) 22 | @ActiveProfiles("mysql") 23 | @WebAppConfiguration 24 | @IntegrationTest 25 | @SpringApplicationConfiguration(classes = {SpringBootRunner.class}) 26 | public class RestTemplateSecureBankApplicationIT { 27 | 28 | 29 | @Test 30 | public void shouldReturnOkIfProvidingGoodCredentials() { 31 | RestTemplate restTemplate = new TestRestTemplate("user", "secret"); 32 | 33 | ResponseEntity responseEntity = restTemplate 34 | .getForEntity("http://localhost:8080/accounts/{accountNumber}", Map.class, 1); 35 | 36 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK)); 37 | } 38 | 39 | 40 | @Test 41 | public void shouldNotGetAccountIfNotProvidingCredentials() { 42 | RestTemplate restTemplate = new TestRestTemplate(); 43 | 44 | ResponseEntity responseEntity = restTemplate 45 | .getForEntity("http://localhost:8080/accounts/{accountNumber}", Void.class, 1); 46 | 47 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.UNAUTHORIZED)); 48 | } 49 | 50 | 51 | @Test 52 | public void shouldNotGetAccountIfProvidingBadCredentials() { 53 | RestTemplate restTemplate = new TestRestTemplate("unknown", "password"); 54 | 55 | ResponseEntity responseEntity = restTemplate 56 | .getForEntity("http://localhost:8080/accounts/{accountNumber}", Void.class, 1); 57 | 58 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.UNAUTHORIZED)); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/controller/TransferTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.controller; 2 | 3 | 4 | import org.junit.BeforeClass; 5 | import org.junit.Test; 6 | 7 | import javax.validation.ConstraintViolation; 8 | import javax.validation.Validation; 9 | import javax.validation.Validator; 10 | import javax.validation.ValidatorFactory; 11 | import java.util.Set; 12 | 13 | import static org.hamcrest.CoreMatchers.is; 14 | import static org.junit.Assert.assertThat; 15 | 16 | public class TransferTest { 17 | 18 | static Validator validator; 19 | Transfer transfer; 20 | 21 | 22 | @BeforeClass 23 | public static void beforeClass() { 24 | ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); 25 | validator = factory.getValidator(); 26 | } 27 | 28 | 29 | @Test 30 | public void shouldAllowZeroAmount() { 31 | transfer = new Transfer(0, 0, 0); 32 | 33 | Set> constraintViolations = 34 | validator.validate(transfer); 35 | 36 | assertThat(constraintViolations.size(), is(0)); 37 | } 38 | 39 | 40 | @Test 41 | public void shouldNotAllowNegativeAmount() { 42 | transfer = new Transfer(0, 0, -1); 43 | 44 | Set> constraintViolations = 45 | validator.validate(transfer); 46 | 47 | assertThat(constraintViolations.size(), is(1)); 48 | assertThat(constraintViolations.iterator().next().getMessage(), 49 | is("Amount must be >= 0")); 50 | } 51 | 52 | 53 | @Test 54 | public void shouldHaveFromAccountNumber() { 55 | transfer = new Transfer(null, 0, 0); 56 | 57 | Set> constraintViolations = 58 | validator.validate(transfer); 59 | 60 | assertThat(constraintViolations.size(), is(1)); 61 | assertThat(constraintViolations.iterator().next().getMessage(), 62 | is("From account number must be set")); 63 | } 64 | 65 | 66 | @Test 67 | public void shouldHaveToAccountNumber() { 68 | transfer = new Transfer(0, null, 0); 69 | 70 | Set> constraintViolations = 71 | validator.validate(transfer); 72 | 73 | assertThat(constraintViolations.size(), is(1)); 74 | assertThat(constraintViolations.iterator().next().getMessage(), 75 | is("To account number must be set")); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/security/SecureAccountServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.jayway.security; 2 | 3 | import com.jayway.service.AccountService; 4 | import com.jayway.service.ImmutableAccount; 5 | import com.jayway.service.UnknownAccountException; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Primary; 8 | import org.springframework.security.access.prepost.PreAuthorize; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | 13 | @Primary 14 | @Service 15 | class SecureAccountServiceImpl implements AccountService { 16 | 17 | private final AccountService accountService; 18 | 19 | 20 | @Autowired 21 | SecureAccountServiceImpl(AccountService accountService) { 22 | this.accountService = accountService; 23 | } 24 | 25 | 26 | @PreAuthorize("hasAuthority('ACCOUNT_OWNER')") 27 | @Override 28 | public ImmutableAccount get(Integer accountNumber) throws UnknownAccountException { 29 | return accountService.get(accountNumber); 30 | } 31 | 32 | 33 | @PreAuthorize("hasAuthority('ACCOUNT_OWNER')") 34 | @Override 35 | public void deposit(Integer accountNumber, int amount) throws UnknownAccountException { 36 | accountService.deposit(accountNumber, amount); 37 | } 38 | 39 | 40 | @PreAuthorize("hasAuthority('ACCOUNT_OWNER')") 41 | @Override 42 | public ImmutableAccount withdraw(Integer accountNumber, int amount) throws UnknownAccountException { 43 | return accountService.withdraw(accountNumber, amount); 44 | } 45 | 46 | 47 | @PreAuthorize("hasAuthority('ACCOUNT_OWNER')") 48 | @Override 49 | public void transfer(Integer fromAccountNumber, Integer toAccountNumber, int amount) throws UnknownAccountException { 50 | accountService.transfer(fromAccountNumber, toAccountNumber, amount); 51 | } 52 | 53 | 54 | @PreAuthorize("hasAuthority('ACCOUNT_OWNER')") 55 | @Override 56 | public Integer createAccount() { 57 | return accountService.createAccount(); 58 | } 59 | 60 | 61 | @PreAuthorize("hasAuthority('ACCOUNT_OWNER')") 62 | @Override 63 | public void deleteAccount(Integer accountNumber) throws UnknownAccountException { 64 | accountService.deleteAccount(accountNumber); 65 | } 66 | 67 | 68 | @PreAuthorize("hasAuthority('ACCOUNT_OWNER')") 69 | @Override 70 | public List getAllAccountNumbers() { 71 | return accountService.getAllAccountNumbers(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/repository/EmbeddedDbJavaConfig.java: -------------------------------------------------------------------------------- 1 | package com.jayway.repository; 2 | 3 | import org.junit.Ignore; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 7 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; 8 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 9 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 10 | import org.springframework.orm.hibernate4.SpringSessionContext; 11 | import org.springframework.orm.jpa.JpaTransactionManager; 12 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 13 | import org.springframework.orm.jpa.vendor.Database; 14 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 15 | import org.springframework.transaction.PlatformTransactionManager; 16 | 17 | import java.util.Properties; 18 | 19 | @Ignore("not a test") 20 | @Configuration 21 | @EnableJpaRepositories("com.jayway.repository") 22 | public class EmbeddedDbJavaConfig { 23 | 24 | @Bean(destroyMethod = "shutdown") 25 | public EmbeddedDatabase dataSource() { 26 | return new EmbeddedDatabaseBuilder(). 27 | setType(EmbeddedDatabaseType.H2). 28 | addScript("db-compatibility-mode.sql"). 29 | addScript("db-schema.sql"). 30 | addScript("db-test-data.sql"). 31 | build(); 32 | } 33 | 34 | @Bean 35 | public LocalContainerEntityManagerFactoryBean entityManagerFactory() { 36 | LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); 37 | entityManagerFactoryBean.setDataSource(dataSource()); 38 | entityManagerFactoryBean.setPackagesToScan(AccountRepository.class.getPackage().getName()); 39 | entityManagerFactoryBean.setJpaProperties(new Properties() {{ 40 | put("hibernate.current_session_context_class", SpringSessionContext.class.getName()); 41 | }}); 42 | entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter() {{ 43 | setDatabase(Database.H2); 44 | }}); 45 | return entityManagerFactoryBean; 46 | } 47 | 48 | @Bean 49 | public PlatformTransactionManager transactionManager() { 50 | JpaTransactionManager transactionManager = new JpaTransactionManager(); 51 | transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); 52 | return transactionManager; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/config/MySqlRepositoryConfig.java: -------------------------------------------------------------------------------- 1 | package com.jayway.config; 2 | 3 | import com.jayway.repository.AccountRepository; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 8 | import org.springframework.jdbc.datasource.DriverManagerDataSource; 9 | import org.springframework.orm.hibernate4.SpringSessionContext; 10 | import org.springframework.orm.jpa.JpaTransactionManager; 11 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 12 | import org.springframework.orm.jpa.vendor.Database; 13 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 14 | import org.springframework.transaction.PlatformTransactionManager; 15 | 16 | import javax.sql.DataSource; 17 | import java.util.Properties; 18 | 19 | @Profile("mysql") 20 | @Configuration 21 | @EnableJpaRepositories("com.jayway.repository") 22 | public class MySqlRepositoryConfig implements RepositoryConfig { 23 | 24 | @Bean 25 | @Override 26 | public DataSource dataSource() { 27 | DriverManagerDataSource dataSource = new DriverManagerDataSource(); 28 | dataSource.setDriverClassName("com.mysql.jdbc.Driver"); 29 | dataSource.setUrl("jdbc:mysql://localhost:3306/spring_testing"); 30 | dataSource.setUsername("root"); 31 | return dataSource; 32 | } 33 | 34 | @Bean 35 | @Override 36 | public LocalContainerEntityManagerFactoryBean entityManagerFactory() { 37 | LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); 38 | entityManagerFactoryBean.setDataSource(dataSource()); 39 | entityManagerFactoryBean.setPackagesToScan(AccountRepository.class.getPackage().getName()); 40 | entityManagerFactoryBean.setJpaProperties(new Properties() {{ 41 | put("hibernate.current_session_context_class", SpringSessionContext.class.getName()); 42 | }}); 43 | entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter() {{ 44 | setDatabase(Database.MYSQL); 45 | }}); 46 | return entityManagerFactoryBean; 47 | } 48 | 49 | @Bean 50 | @Override 51 | public PlatformTransactionManager transactionManager() { 52 | JpaTransactionManager transactionManager = new JpaTransactionManager(); 53 | transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); 54 | return transactionManager; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/config/H2RepositoryConfig.java: -------------------------------------------------------------------------------- 1 | package com.jayway.config; 2 | 3 | import com.jayway.repository.AccountRepository; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 8 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; 9 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 10 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 11 | import org.springframework.orm.hibernate4.SpringSessionContext; 12 | import org.springframework.orm.jpa.JpaTransactionManager; 13 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 14 | import org.springframework.orm.jpa.vendor.Database; 15 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 16 | import org.springframework.transaction.PlatformTransactionManager; 17 | 18 | import java.util.Properties; 19 | 20 | @Profile("h2") 21 | @Configuration 22 | @EnableJpaRepositories("com.jayway.repository") 23 | public class H2RepositoryConfig implements RepositoryConfig { 24 | 25 | @Bean(destroyMethod = "shutdown") 26 | @Override 27 | public EmbeddedDatabase dataSource() { 28 | return new EmbeddedDatabaseBuilder(). 29 | setType(EmbeddedDatabaseType.H2). 30 | addScript("db-compatibility-mode.sql"). 31 | addScript("db-schema.sql"). 32 | addScript("db-test-data.sql"). 33 | build(); 34 | } 35 | 36 | @Bean 37 | @Override 38 | public LocalContainerEntityManagerFactoryBean entityManagerFactory() { 39 | LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); 40 | entityManagerFactoryBean.setDataSource(dataSource()); 41 | entityManagerFactoryBean.setPackagesToScan(AccountRepository.class.getPackage().getName()); 42 | entityManagerFactoryBean.setJpaProperties(new Properties() {{ 43 | put("hibernate.current_session_context_class", SpringSessionContext.class.getName()); 44 | }}); 45 | entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter() {{ 46 | setDatabase(Database.H2); 47 | }}); 48 | return entityManagerFactoryBean; 49 | } 50 | 51 | @Bean 52 | @Override 53 | public PlatformTransactionManager transactionManager() { 54 | JpaTransactionManager transactionManager = new JpaTransactionManager(); 55 | transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); 56 | return transactionManager; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/repository/AccountEntityTransactionalTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.repository; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.test.context.ContextConfiguration; 7 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8 | import org.springframework.test.context.transaction.TransactionConfiguration; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import javax.persistence.EntityManager; 12 | import javax.persistence.PersistenceContext; 13 | import javax.validation.ConstraintViolationException; 14 | 15 | import static org.hamcrest.CoreMatchers.is; 16 | import static org.junit.Assert.assertThat; 17 | 18 | 19 | @RunWith(SpringJUnit4ClassRunner.class) 20 | @ContextConfiguration(classes = EmbeddedDbJavaConfig.class) 21 | // @ContextConfiguration("/embedded-db-application-context.xml") 22 | @Transactional 23 | @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true) 24 | public class AccountEntityTransactionalTest { 25 | 26 | @PersistenceContext 27 | EntityManager entityManager; 28 | 29 | @Autowired 30 | AccountRepository accountRepository; 31 | 32 | 33 | @Test 34 | public void canFindAccount() { 35 | AccountEntity account = accountRepository.findOne(1); 36 | 37 | assertThat(account.getAccountNumber(), is(1)); 38 | assertThat(account.getBalance(), is(100)); 39 | } 40 | 41 | 42 | @Test 43 | public void shouldDeposit() { 44 | AccountEntity account = accountRepository.findOne(1); 45 | account.deposit(50); 46 | 47 | entityManager.flush(); 48 | 49 | AccountEntity updated = accountRepository.findOne(1); 50 | assertThat(updated.getBalance(), is(150)); 51 | } 52 | 53 | 54 | @Test 55 | public void verifyBalance() { 56 | AccountEntity account = accountRepository.findOne(1); 57 | 58 | assertThat(account.getAccountNumber(), is(1)); 59 | assertThat(account.getBalance(), is(100)); 60 | } 61 | 62 | 63 | @Test 64 | public void shouldWithdraw() { 65 | AccountEntity account = accountRepository.findOne(1); 66 | account.withdraw(20); 67 | 68 | entityManager.flush(); 69 | 70 | AccountEntity updated = accountRepository.findOne(1); 71 | assertThat(updated.getBalance(), is(80)); 72 | } 73 | 74 | 75 | @Test(expected = ConstraintViolationException.class) 76 | public void shouldNotAcceptNegativeBalance() { 77 | AccountEntity account = accountRepository.findOne(1); 78 | account.withdraw(200); 79 | 80 | entityManager.flush(); 81 | 82 | AccountEntity updated = accountRepository.findOne(1); 83 | assertThat(updated.getBalance(), is(-100)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/jayway/service/AccountServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.jayway.service; 2 | 3 | import com.jayway.repository.AccountEntity; 4 | import com.jayway.repository.AccountRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.dao.EmptyResultDataAccessException; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @Transactional 14 | @Service 15 | class AccountServiceImpl implements AccountService { 16 | 17 | private final AccountRepository accountRepository; 18 | 19 | 20 | @Autowired 21 | AccountServiceImpl(AccountRepository accountRepository) { 22 | this.accountRepository = accountRepository; 23 | } 24 | 25 | 26 | @Transactional(readOnly = true) 27 | @Override 28 | public ImmutableAccount get(Integer accountNumber) { 29 | AccountEntity account = getAccountEntity(accountNumber); 30 | return new ImmutableAccount(account.getAccountNumber(), account.getBalance()); 31 | } 32 | 33 | 34 | @Override 35 | public void deposit(Integer accountNumber, int amount) { 36 | AccountEntity account = getAccountEntity(accountNumber); 37 | account.deposit(amount); 38 | accountRepository.save(account); 39 | } 40 | 41 | 42 | @Override 43 | public ImmutableAccount withdraw(Integer accountNumber, int amount) { 44 | AccountEntity account = getAccountEntity(accountNumber); 45 | account.withdraw(amount); 46 | AccountEntity savedAccount = accountRepository.save(account); 47 | return new ImmutableAccount(savedAccount.getAccountNumber(), savedAccount.getBalance()); 48 | } 49 | 50 | 51 | @Override 52 | public void transfer(Integer fromAccountNumber, Integer toAccountNumber, int amount) { 53 | AccountEntity fromAccount = getAccountEntity(fromAccountNumber); 54 | AccountEntity toAccount = getAccountEntity(toAccountNumber); 55 | fromAccount.withdraw(amount); 56 | toAccount.deposit(amount); 57 | accountRepository.save(fromAccount); 58 | accountRepository.save(toAccount); 59 | } 60 | 61 | 62 | @Override 63 | public Integer createAccount() { 64 | AccountEntity account = new AccountEntity(); 65 | AccountEntity savedAccount = accountRepository.save(account); 66 | return savedAccount.getAccountNumber(); 67 | } 68 | 69 | 70 | @Override 71 | public void deleteAccount(Integer accountNumber) throws UnknownAccountException { 72 | try { 73 | accountRepository.delete(accountNumber); 74 | } catch (EmptyResultDataAccessException e) { 75 | throw new UnknownAccountException(accountNumber, e); 76 | } 77 | } 78 | 79 | 80 | @Transactional(readOnly = true) 81 | @Override 82 | public List getAllAccountNumbers() { 83 | List accounts = accountRepository.findAll(); 84 | List result = new ArrayList<>(accounts.size()); 85 | for (AccountEntity account : accounts) { 86 | result.add(account.getAccountNumber()); 87 | } 88 | return result; 89 | } 90 | 91 | 92 | private AccountEntity getAccountEntity(Integer accountNumber) throws UnknownAccountException { 93 | AccountEntity account = accountRepository.findOne(accountNumber); 94 | if (account == null) { 95 | throw new UnknownAccountException(accountNumber); 96 | } 97 | return account; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Spring Testing 2 | 3 | This is a simplistic demo project intended to show how some Spring features can be used to 4 | improve your Spring test suite. The project has been used as demo during conference 5 | sessions. During SpringOne 2GX 2013 the session was recorded and you can watch it on 6 | [YouTube](https://www.youtube.com/watch?v=LYVJ69h76nw). Below you will find references to 7 | where the concepts in the presentation have been used in the source code. 8 | 9 | 10 | ### Embedded database 11 | 12 | An example of an application context with an embedded database can be found in 13 | [EmbeddedDbJavaConfig](src/test/java/com/jayway/repository/EmbeddedDbJavaConfig.java) class. 14 | 15 | Test of the example database can be seen in [EmbeddedDbJavaConfigTest](src/test/java/com/jayway/repository/EmbeddedDbJavaConfigTest.java). 16 | 17 | 18 | ### Transactional Tests 19 | 20 | Transactions are used in some tests, e.g. [AccountEntityTransactionalTest](src/test/java/com/jayway/repository/AccountEntityTransactionalTest.java). 21 | 22 | Remember to `flush()` your JPA entity managers and your Hibernate sessions to avoid false 23 | positives when testing. 24 | 25 | 26 | ### Spring Profiles 27 | 28 | The [RepositoryConfig](src/main/java/com/jayway/config/RepositoryConfig.java) interface 29 | has been implemented using three different profiles: 30 | 31 | * `h2` uses a H2 in-memory database, see [H2RepositoryConfig](src/main/java/com/jayway/config/H2RepositoryConfig.java). 32 | * `mysql` connects to a local MySQL database, see [MySqlRepositoryConfig](src/main/java/com/jayway/config/MySqlRepositoryConfig.java). 33 | * `prod` simulates a JNDI database resource lookup, see [JndiRepositoryConfig](src/main/java/com/jayway/config/JndiRepositoryConfig.java). 34 | 35 | All three repository config classes above have been imported by the [ApplicationConfig](src/main/java/com/jayway/config/ApplicationConfig.java) 36 | class, but only one can be used at the time, by settings the `spring.profiles.active` environment variable. The `prod` 37 | profile has been set to the default profile by using the `spring.profiles.default` 38 | context parameter in [web.xml](src/main/webapp/WEB-INF/web.xml). 39 | 40 | In some tests, like the [AccountServiceImplTest](src/test/java/com/jayway/service/AccountServiceImplTest.java), 41 | the `@ActiveProfiles` annotation has been used to specify which profile should be active 42 | in the test. 43 | 44 | 45 | ### Mockito 46 | 47 | A pure Mockito test that does not use any Spring related tools is exemplified in 48 | [BankControllerBasicTest](src/test/java/com/jayway/controller/BankControllerBasicTest.java). 49 | 50 | If a mock object is used as a Spring bean in an application context, it should be `reset()` 51 | `@Before` or `@After` a test to certify that it is always in clean state before the next 52 | test is executed. 53 | 54 | 55 | ### Controller Tests 56 | 57 | The test [BankControllerMvcTest](src/test/java/com/jayway/controller/BankControllerMvcTest.java) 58 | uses the `MockMvc` class to verify request mapping, serialization, response codes, etc. 59 | 60 | 61 | ### Integration Tests 62 | 63 | The [BankApplicationTest](src/test/java/com/jayway/application/BankApplicationTest.java) 64 | is a Spring based integration tests that tests the entire stack based on pure Spring features, 65 | `MockMvc`, `@Transactional`, embedded database, etc. 66 | 67 | The `maven-failsafe-plugin` has been added to the [pom.xml] file to automate the 68 | integration tests. It executes the integration test during the integration-test phase 69 | (in contrast to the `maven-surefire-plugin` that executes tests in the test phase). 70 | Moreover, it is activated by using the Maven `itest` profile. 71 | 72 | The [RestTemplateBankApplicationIT](src/test/java/com/jayway/application/RestTemplateBankApplicationIT.java) 73 | is an integration test based on Spring's `RestTemplate`. A similar test based on 74 | [REST-assured](https://code.google.com/p/rest-assured/) can be found in 75 | [RestAssuredBankApplicationIT](src/test/java/com/jayway/application/RestAssuredBankApplicationIT.java) 76 | In order to execute them, you must have a MySQL running locally that Spring can connect to. 77 | Alternatively, you can change the profile from `mysql` to `h2` the tests will use 78 | and embedded H2 database instead. -------------------------------------------------------------------------------- /src/main/java/com/jayway/controller/BankController.java: -------------------------------------------------------------------------------- 1 | package com.jayway.controller; 2 | 3 | import com.jayway.service.AccountService; 4 | import com.jayway.service.ImmutableAccount; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.transaction.TransactionSystemException; 9 | import org.springframework.web.bind.annotation.ExceptionHandler; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | import org.springframework.web.bind.annotation.ResponseStatus; 15 | import org.springframework.web.bind.annotation.RestController; 16 | import org.springframework.web.util.UriTemplate; 17 | 18 | import javax.servlet.http.HttpServletRequest; 19 | import javax.servlet.http.HttpServletResponse; 20 | import javax.validation.Valid; 21 | import java.util.List; 22 | 23 | @RestController 24 | class BankController { 25 | 26 | private final AccountService accountService; 27 | 28 | 29 | @Autowired 30 | BankController(AccountService accountService) { 31 | this.accountService = accountService; 32 | } 33 | 34 | 35 | @RequestMapping(value = "/accounts/{accountNumber}", 36 | method = RequestMethod.GET, 37 | produces = MediaType.APPLICATION_JSON_VALUE) 38 | ImmutableAccount get(@PathVariable("accountNumber") int accountNumber) { 39 | return accountService.get(accountNumber); 40 | } 41 | 42 | 43 | @RequestMapping(value = "/accounts/{accountNumber}/deposit", 44 | method = RequestMethod.POST, 45 | consumes = MediaType.APPLICATION_JSON_VALUE) 46 | @ResponseStatus(HttpStatus.NO_CONTENT) // 204 47 | void deposit(@PathVariable("accountNumber") int accountNumber, 48 | @Valid @RequestBody Amount amount) { 49 | accountService.deposit(accountNumber, amount.getAmount()); 50 | } 51 | 52 | 53 | @RequestMapping(value = "/accounts/{accountNumber}/withdraw", 54 | method = RequestMethod.POST, 55 | consumes = MediaType.APPLICATION_JSON_VALUE, 56 | produces = MediaType.APPLICATION_JSON_VALUE) 57 | ImmutableAccount withdraw(@PathVariable("accountNumber") int accountNumber, 58 | @Valid @RequestBody Amount amount) { 59 | return accountService.withdraw(accountNumber, amount.getAmount()); 60 | } 61 | 62 | 63 | @RequestMapping(value = "/accounts", 64 | method = RequestMethod.GET, 65 | produces = MediaType.APPLICATION_JSON_VALUE) 66 | List getAll() { 67 | return accountService.getAllAccountNumbers(); 68 | } 69 | 70 | 71 | @RequestMapping(value = "/accounts", 72 | method = RequestMethod.POST, 73 | produces = MediaType.APPLICATION_JSON_VALUE) 74 | @ResponseStatus(HttpStatus.CREATED) // 201 75 | void create(HttpServletRequest request, HttpServletResponse response) { 76 | int accountNumber = accountService.createAccount(); 77 | String locationHeader = createLocationHeader(request, accountNumber); 78 | response.addHeader("Location", locationHeader); 79 | } 80 | 81 | 82 | @RequestMapping(value = "/accounts/{accountNumber}", 83 | method = RequestMethod.DELETE) 84 | @ResponseStatus(HttpStatus.NO_CONTENT) // 204 85 | void delete(@PathVariable("accountNumber") int accountNumber) { 86 | accountService.deleteAccount(accountNumber); 87 | } 88 | 89 | 90 | @RequestMapping(value = "/transfer", 91 | method = RequestMethod.POST, 92 | consumes = MediaType.APPLICATION_JSON_VALUE) 93 | @ResponseStatus(HttpStatus.NO_CONTENT) // 204 94 | void transfer(@Valid @RequestBody Transfer transfer) { 95 | accountService.transfer(transfer.getFromAccountNumber(), transfer.getToAccountNumber(), transfer.getAmount()); 96 | } 97 | 98 | 99 | @ExceptionHandler(TransactionSystemException.class) 100 | @ResponseStatus(HttpStatus.CONFLICT) // 409 101 | void constraintViolation() {} 102 | 103 | 104 | private String createLocationHeader(HttpServletRequest request, int accountNumber) { 105 | StringBuffer url = request.getRequestURL(); 106 | UriTemplate template = new UriTemplate(url.append("/{accountNumber}").toString()); 107 | return template.expand(accountNumber).toASCIIString(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/resources/application-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | org.springframework.orm.hibernate4.SpringSessionContext 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.springframework.orm.hibernate4.SpringSessionContext 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/security/SecureAccountServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.security; 2 | 3 | import com.jayway.config.ApplicationConfig; 4 | import com.jayway.config.SecurityConfig; 5 | import com.jayway.domain.Account; 6 | import com.jayway.service.AccountService; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.security.access.AccessDeniedException; 12 | import org.springframework.security.authentication.TestingAuthenticationToken; 13 | import org.springframework.security.core.GrantedAuthority; 14 | import org.springframework.security.core.authority.AuthorityUtils; 15 | import org.springframework.security.core.context.SecurityContextHolder; 16 | import org.springframework.test.context.ActiveProfiles; 17 | import org.springframework.test.context.ContextConfiguration; 18 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 19 | import org.springframework.transaction.annotation.Transactional; 20 | 21 | import java.util.List; 22 | 23 | import static org.hamcrest.CoreMatchers.notNullValue; 24 | import static org.junit.Assert.assertThat; 25 | 26 | @RunWith(SpringJUnit4ClassRunner.class) 27 | @ContextConfiguration(classes = {ApplicationConfig.class, SecurityConfig.class}) 28 | // @ContextConfiguration({"/application-context.xml", "/security-context.xml"}) 29 | @ActiveProfiles("h2") 30 | @Transactional 31 | public class SecureAccountServiceImplTest { 32 | 33 | @Autowired 34 | AccountService secureAccountService; 35 | 36 | 37 | @Before 38 | public void setUp() { 39 | SecurityContextHolder.clearContext(); 40 | } 41 | 42 | 43 | @Test 44 | public void accountOwnerShouldGetAccount() { 45 | List authorities = AuthorityUtils.createAuthorityList("ACCOUNT_OWNER"); 46 | authenticateWithAuthorities(authorities); 47 | 48 | Account account = secureAccountService.get(1); 49 | 50 | assertThat(account, notNullValue()); 51 | } 52 | 53 | 54 | @Test(expected = AccessDeniedException.class) 55 | public void unAuthorizedShouldNotGetAccount() { 56 | authenticateWithAuthorities(AuthorityUtils.NO_AUTHORITIES); 57 | 58 | secureAccountService.get(1); 59 | } 60 | 61 | 62 | @Test 63 | public void accountOwnerShouldDeposit() { 64 | List authorities = AuthorityUtils.createAuthorityList("ACCOUNT_OWNER"); 65 | authenticateWithAuthorities(authorities); 66 | 67 | secureAccountService.deposit(1, 100); 68 | } 69 | 70 | 71 | @Test(expected = AccessDeniedException.class) 72 | public void unAuthorizedShouldNotDeposit() { 73 | authenticateWithAuthorities(AuthorityUtils.NO_AUTHORITIES); 74 | 75 | secureAccountService.deposit(1, 100); 76 | } 77 | 78 | 79 | @Test 80 | public void accountOwnerShouldWithdraw() { 81 | List authorities = AuthorityUtils.createAuthorityList("ACCOUNT_OWNER"); 82 | authenticateWithAuthorities(authorities); 83 | 84 | Account account = secureAccountService.withdraw(1, 50); 85 | 86 | assertThat(account, notNullValue()); 87 | } 88 | 89 | 90 | @Test(expected = AccessDeniedException.class) 91 | public void unAuthorizedShouldNotWithdraw() { 92 | authenticateWithAuthorities(AuthorityUtils.NO_AUTHORITIES); 93 | 94 | secureAccountService.withdraw(1, 50); 95 | } 96 | 97 | 98 | @Test 99 | public void accountOwnerShouldTransfer() { 100 | List authorities = AuthorityUtils.createAuthorityList("ACCOUNT_OWNER"); 101 | authenticateWithAuthorities(authorities); 102 | 103 | secureAccountService.transfer(1, 2, 10); 104 | } 105 | 106 | 107 | @Test(expected = AccessDeniedException.class) 108 | public void unAuthorizedShouldNotTransfer() { 109 | authenticateWithAuthorities(AuthorityUtils.NO_AUTHORITIES); 110 | 111 | secureAccountService.transfer(1, 2, 10); 112 | } 113 | 114 | 115 | @Test 116 | public void accountOwnerShouldGetAllAccountNumbers() { 117 | List authorities = AuthorityUtils.createAuthorityList("ACCOUNT_OWNER"); 118 | authenticateWithAuthorities(authorities); 119 | 120 | List allAccounts = secureAccountService.getAllAccountNumbers(); 121 | 122 | assertThat(allAccounts, notNullValue()); 123 | } 124 | 125 | 126 | @Test(expected = AccessDeniedException.class) 127 | public void unAuthorizedShouldGetAllAccountNumbers() { 128 | authenticateWithAuthorities(AuthorityUtils.NO_AUTHORITIES); 129 | 130 | secureAccountService.getAllAccountNumbers(); 131 | } 132 | 133 | 134 | private void authenticateWithAuthorities(List authorities) { 135 | TestingAuthenticationToken authenticationToken = new TestingAuthenticationToken("name", "password", authorities); 136 | authenticationToken.setAuthenticated(true); 137 | SecurityContextHolder.getContext().setAuthentication(authenticationToken); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/service/AccountServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.service; 2 | 3 | import com.jayway.config.ApplicationConfig; 4 | import com.jayway.domain.Account; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.jdbc.core.JdbcTemplate; 10 | import org.springframework.test.context.ActiveProfiles; 11 | import org.springframework.test.context.ContextConfiguration; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | import org.springframework.test.context.transaction.TransactionConfiguration; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | import javax.persistence.EntityManager; 17 | import javax.persistence.PersistenceContext; 18 | import javax.sql.DataSource; 19 | import javax.validation.ConstraintViolationException; 20 | import java.util.List; 21 | 22 | import static org.hamcrest.CoreMatchers.is; 23 | import static org.hamcrest.Matchers.hasItems; 24 | import static org.junit.Assert.assertThat; 25 | import static org.junit.Assert.fail; 26 | 27 | 28 | @RunWith(SpringJUnit4ClassRunner.class) 29 | @ContextConfiguration(classes = ApplicationConfig.class) 30 | // @ContextConfiguration("classpath:application-context.xml") 31 | @ActiveProfiles("h2") 32 | @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true) 33 | @Transactional 34 | public class AccountServiceImplTest { 35 | 36 | @PersistenceContext 37 | EntityManager entityManager; 38 | 39 | @Autowired 40 | AccountService accountService; 41 | 42 | @Autowired 43 | DataSource dataSource; 44 | 45 | JdbcTemplate jdbcTemplate; 46 | 47 | 48 | @Before 49 | public void setUp() { 50 | jdbcTemplate = new JdbcTemplate(dataSource); 51 | } 52 | 53 | 54 | @Test 55 | public void shouldGetAccount() { 56 | Account account = accountService.get(1); 57 | 58 | assertThat(account.getAccountNumber(), is(1)); 59 | assertThat(account.getBalance(), is(100)); 60 | } 61 | 62 | 63 | @Test(expected = UnknownAccountException.class) 64 | public void shouldThrowExceptionForFindingUnknownAccountNumber() { 65 | accountService.get(-1); 66 | } 67 | 68 | 69 | @Test 70 | public void shouldDeposit() { 71 | accountService.deposit(1, 100); 72 | 73 | entityManager.flush(); 74 | 75 | int balance = getBalance(1); 76 | assertThat(balance, is(200)); 77 | } 78 | 79 | 80 | @Test 81 | public void shouldWithdraw() { 82 | accountService.withdraw(1, 50); 83 | 84 | entityManager.flush(); 85 | 86 | int balance = getBalance(1); 87 | assertThat(balance, is(50)); 88 | } 89 | 90 | 91 | @Test 92 | public void shouldNotOverdraw() { 93 | try { 94 | accountService.withdraw(1, 200); 95 | entityManager.flush(); 96 | fail("Expected ConstraintViolationException"); 97 | } catch (ConstraintViolationException e) { 98 | // Expected 99 | } 100 | 101 | int balance = getBalance(1); 102 | assertThat(balance, is(100)); 103 | } 104 | 105 | 106 | @Test 107 | public void shouldTransfer() { 108 | accountService.transfer(1, 2, 10); 109 | entityManager.flush(); 110 | 111 | int firstBalance = getBalance(1); 112 | assertThat(firstBalance, is(90)); 113 | 114 | int secondBalance = getBalance(2); 115 | assertThat(secondBalance, is(210)); 116 | } 117 | 118 | 119 | @Test 120 | public void shouldNotTransferIfOverdraw() { 121 | try { 122 | accountService.transfer(1, 2, 200); 123 | entityManager.flush(); 124 | fail("Expected ConstraintViolationException"); 125 | } catch (ConstraintViolationException e) { 126 | // Expected 127 | } 128 | 129 | int firstBalance = getBalance(1); 130 | assertThat(firstBalance, is(100)); 131 | 132 | int secondBalance = getBalance(2); 133 | assertThat(secondBalance, is(200)); 134 | } 135 | 136 | 137 | @Test 138 | public void shouldNotTransferFromUnknownAccount() { 139 | try { 140 | accountService.transfer(-1, 2, 50); 141 | entityManager.flush(); 142 | fail("Expected UnknownAccountException"); 143 | } catch (UnknownAccountException e) { 144 | // Expected 145 | } 146 | 147 | int secondBalance = getBalance(2); 148 | assertThat(secondBalance, is(200)); 149 | } 150 | 151 | 152 | @Test 153 | public void shouldNotTransferToUnknownAccount() { 154 | try { 155 | accountService.transfer(1, -2, 50); 156 | entityManager.flush(); 157 | fail("Expected UnknownAccountException"); 158 | } catch (UnknownAccountException e) { 159 | // Expected 160 | } 161 | 162 | int balance = getBalance(1); 163 | assertThat(balance, is(100)); 164 | } 165 | 166 | 167 | @Test 168 | public void shouldGetAllAccountNumbers() { 169 | List allAccounts = accountService.getAllAccountNumbers(); 170 | 171 | assertThat(allAccounts, hasItems(1, 2)); 172 | } 173 | 174 | 175 | @Test(expected = UnknownAccountException.class) 176 | public void shouldThrowExceptionWhenDeletingUnknownAccountNumber() { 177 | accountService.deleteAccount(-1); 178 | } 179 | 180 | 181 | int getBalance(Integer accountNumber) { 182 | return jdbcTemplate.queryForObject("SELECT balance FROM account_t WHERE account_number = ?", Integer.class, accountNumber); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/application/BankApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.application; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.jayway.config.ApplicationConfig; 6 | import com.jayway.config.WebConfig; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.test.context.ActiveProfiles; 13 | import org.springframework.test.context.ContextConfiguration; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | import org.springframework.test.context.web.WebAppConfiguration; 16 | import org.springframework.test.web.servlet.MockMvc; 17 | import org.springframework.test.web.servlet.result.MockMvcResultHandlers; 18 | import org.springframework.transaction.annotation.Transactional; 19 | import org.springframework.web.context.WebApplicationContext; 20 | 21 | import java.util.Collections; 22 | import java.util.Map; 23 | 24 | import static org.hamcrest.Matchers.is; 25 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 26 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 27 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; 28 | 29 | @RunWith(SpringJUnit4ClassRunner.class) 30 | @ContextConfiguration(classes = {WebConfig.class, ApplicationConfig.class}) 31 | @ActiveProfiles({"h2", "disableSecurity"}) 32 | @WebAppConfiguration 33 | @Transactional 34 | public class BankApplicationTest { 35 | 36 | @Autowired 37 | WebApplicationContext webApplicationContext; 38 | 39 | MockMvc mockMvc; 40 | 41 | 42 | @Before 43 | public void setup() { 44 | mockMvc = webAppContextSetup(webApplicationContext).build(); 45 | } 46 | 47 | 48 | @Test 49 | public void shouldPrint() throws Exception { 50 | mockMvc 51 | .perform(get("/accounts/1") 52 | .accept(MediaType.APPLICATION_JSON)) 53 | .andDo(MockMvcResultHandlers.print()); 54 | } 55 | 56 | 57 | @Test 58 | public void shouldGetAccount() throws Exception { 59 | mockMvc 60 | .perform(get("/accounts/1") 61 | .accept(MediaType.APPLICATION_JSON)) 62 | .andExpect(status().isOk()) 63 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 64 | .andExpect(jsonPath("accountNumber").value(1)) 65 | .andExpect(jsonPath("balance").value(100)); 66 | } 67 | 68 | 69 | @Test 70 | public void shouldGetAllAccounts() throws Exception { 71 | mockMvc 72 | .perform(get("/accounts") 73 | .accept(MediaType.APPLICATION_JSON)) 74 | .andExpect(status().isOk()) 75 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 76 | .andExpect(jsonPath("[0]").value(1)) 77 | .andExpect(jsonPath("[1]").value(2)); 78 | } 79 | 80 | 81 | @Test 82 | public void shouldDepositToAccount() throws Exception { 83 | Map body = Collections.singletonMap("amount", 50); 84 | String json = toJsonString(body); 85 | 86 | mockMvc 87 | .perform(post("/accounts/1/deposit") 88 | .contentType(MediaType.APPLICATION_JSON) 89 | .content(json)) 90 | .andExpect(status().isNoContent()); 91 | } 92 | 93 | 94 | @Test 95 | public void shouldDeleteAccount() throws Exception { 96 | mockMvc 97 | .perform(delete("/accounts/1")) 98 | .andExpect(status().isNoContent()); 99 | } 100 | 101 | 102 | @Test 103 | public void shouldNotDepositNegativeAmount() throws Exception { 104 | Map body = Collections.singletonMap("amount", -50); 105 | String json = toJsonString(body); 106 | 107 | mockMvc 108 | .perform(post("/accounts/1/deposit") 109 | .contentType(MediaType.APPLICATION_JSON) 110 | .content(json)) 111 | .andExpect(status().isBadRequest()); 112 | } 113 | 114 | 115 | @Test 116 | public void shouldWithdrawFromAccount() throws Exception { 117 | Map body = Collections.singletonMap("amount", 50); 118 | String json = toJsonString(body); 119 | 120 | mockMvc 121 | .perform(post("/accounts/1/withdraw") 122 | .accept(MediaType.APPLICATION_JSON) 123 | .contentType(MediaType.APPLICATION_JSON) 124 | .content(json)) 125 | .andExpect(status().isOk()) 126 | .andExpect(jsonPath("accountNumber", is(1))) 127 | .andExpect(jsonPath("balance", is(50))); 128 | } 129 | 130 | 131 | @Test 132 | public void shouldNotWithdrawNegativeAmount() throws Exception { 133 | Map body = Collections.singletonMap("amount", -50); 134 | String json = toJsonString(body); 135 | 136 | mockMvc 137 | .perform(post("/accounts/1/withdraw") 138 | .accept(MediaType.APPLICATION_JSON) 139 | .contentType(MediaType.APPLICATION_JSON) 140 | .content(json)) 141 | .andExpect(status().isBadRequest()); 142 | } 143 | 144 | 145 | private static String toJsonString(Map map) { 146 | ObjectMapper objectMapper = new ObjectMapper(); 147 | try { 148 | return objectMapper.writeValueAsString(map); 149 | } catch (JsonProcessingException e) { 150 | throw new RuntimeException(e); 151 | } 152 | } 153 | } 154 | 155 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/controller/BankControllerMvcTest.java: -------------------------------------------------------------------------------- 1 | package com.jayway.controller; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.jayway.service.AccountService; 6 | import com.jayway.service.ImmutableAccount; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.Mock; 11 | import org.mockito.runners.MockitoJUnitRunner; 12 | import org.springframework.http.MediaType; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | import org.springframework.test.web.servlet.result.MockMvcResultHandlers; 15 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 16 | 17 | import java.util.Arrays; 18 | import java.util.Collections; 19 | import java.util.Map; 20 | 21 | import static org.mockito.Mockito.*; 22 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 24 | 25 | @RunWith(MockitoJUnitRunner.class) 26 | public class BankControllerMvcTest { 27 | 28 | @Mock 29 | AccountService accountServiceMock; 30 | 31 | MockMvc mockMvc; 32 | 33 | 34 | @Before 35 | public void setUp() { 36 | mockMvc = MockMvcBuilders 37 | .standaloneSetup(new BankController(accountServiceMock)) 38 | .build(); 39 | } 40 | 41 | 42 | @Test 43 | public void shouldPrint() throws Exception { 44 | ImmutableAccount account = new ImmutableAccount(1, 100); 45 | when(accountServiceMock.get(1)).thenReturn(account); 46 | 47 | mockMvc 48 | .perform(get("/accounts/1") 49 | .accept(MediaType.APPLICATION_JSON)) 50 | .andDo(MockMvcResultHandlers.print()); 51 | } 52 | 53 | 54 | @Test 55 | public void shouldGetAccount() throws Exception { 56 | ImmutableAccount account = new ImmutableAccount(1, 100); 57 | when(accountServiceMock.get(1)).thenReturn(account); 58 | 59 | mockMvc 60 | .perform(get("/accounts/1") 61 | .accept(MediaType.APPLICATION_JSON)) 62 | .andExpect(status().isOk()) 63 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 64 | .andExpect(jsonPath("accountNumber").value(1)) 65 | .andExpect(jsonPath("balance").value(100)); 66 | } 67 | 68 | 69 | @Test 70 | public void shouldGetAllAccounts() throws Exception { 71 | when(accountServiceMock.getAllAccountNumbers()).thenReturn(Arrays.asList(1, 2)); 72 | 73 | mockMvc 74 | .perform(get("/accounts") 75 | .accept(MediaType.APPLICATION_JSON)) 76 | .andExpect(status().isOk()) 77 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 78 | .andExpect(jsonPath("[0]").value(1)) 79 | .andExpect(jsonPath("[1]").value(2)); 80 | } 81 | 82 | 83 | @Test 84 | public void shouldDepositToAccount() throws Exception { 85 | Map body = Collections.singletonMap("amount", 50); 86 | String json = toJsonString(body); 87 | 88 | mockMvc 89 | .perform(post("/accounts/1/deposit") 90 | .contentType(MediaType.APPLICATION_JSON) 91 | .content(json)) 92 | .andExpect(status().isNoContent()); 93 | 94 | verify(accountServiceMock).deposit(1, 50); 95 | } 96 | 97 | 98 | @Test 99 | public void shouldDeleteAccount() throws Exception { 100 | mockMvc 101 | .perform(delete("/accounts/1")) 102 | .andExpect(status().isNoContent()); 103 | 104 | verify(accountServiceMock).deleteAccount(1); 105 | } 106 | 107 | 108 | @Test 109 | public void shouldNotDepositNegativeAmount() throws Exception { 110 | Map body = Collections.singletonMap("amount", -50); 111 | String json = toJsonString(body); 112 | 113 | mockMvc 114 | .perform(post("/accounts/1/deposit") 115 | .contentType(MediaType.APPLICATION_JSON) 116 | .content(json)) 117 | .andExpect(status().isBadRequest()); 118 | 119 | verifyZeroInteractions(accountServiceMock); 120 | } 121 | 122 | 123 | @Test 124 | public void shouldWithdrawFromAccount() throws Exception { 125 | Map body = Collections.singletonMap("amount", 50); 126 | String json = toJsonString(body); 127 | 128 | mockMvc 129 | .perform(post("/accounts/1/withdraw") 130 | .accept(MediaType.APPLICATION_JSON) 131 | .contentType(MediaType.APPLICATION_JSON) 132 | .content(json)) 133 | .andExpect(status().isOk()); 134 | 135 | verify(accountServiceMock).withdraw(1, 50); 136 | } 137 | 138 | 139 | @Test 140 | public void shouldNotWithdrawNegativeAmount() throws Exception { 141 | Map body = Collections.singletonMap("amount", -50); 142 | String json = toJsonString(body); 143 | 144 | mockMvc 145 | .perform(post("/accounts/1/withdraw") 146 | .accept(MediaType.APPLICATION_JSON) 147 | .contentType(MediaType.APPLICATION_JSON) 148 | .content(json)) 149 | .andExpect(status().isBadRequest()); 150 | 151 | verifyZeroInteractions(accountServiceMock); 152 | } 153 | 154 | private String toJsonString(Map map) { 155 | ObjectMapper objectMapper = new ObjectMapper(); 156 | try { 157 | return objectMapper.writeValueAsString(map); 158 | } catch (JsonProcessingException e) { 159 | throw new RuntimeException(e); 160 | } 161 | } 162 | } 163 | 164 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.jayway 6 | spring-testing 7 | 0.1-SNAPSHOT 8 | war 9 | 10 | Spring Testing 11 | 12 | 13 | org.springframework.boot 14 | spring-boot-starter-parent 15 | 1.0.2.RELEASE 16 | 17 | 18 | 19 | 1.7 20 | 9.1.4.v20140401 21 | 3.1.0 22 | com.jayway.config.SpringBootRunner 23 | 24 | 25 | 26 | 27 | Mattias Severson 28 | http://blog.jayway.com/author/mattiasseverson 29 | Jayway 30 | http://www.jayway.com 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-maven-plugin 39 | 40 | standalone 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | itest 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-failsafe-plugin 54 | 55 | 56 | integration-test 57 | 58 | integration-test 59 | 60 | 61 | 62 | verify 63 | 64 | verify 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-starter-web 81 | 82 | 83 | org.springframework.boot 84 | spring-boot-starter-tomcat 85 | 86 | 87 | 88 | 89 | 90 | org.springframework.boot 91 | spring-boot-starter-jetty 92 | 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-starter-security 97 | 98 | 99 | 100 | org.springframework.boot 101 | spring-boot-starter-data-jpa 102 | 103 | 104 | 105 | org.springframework.boot 106 | spring-boot-starter-jdbc 107 | 108 | 109 | 110 | org.springframework.boot 111 | spring-boot-starter-logging 112 | 113 | 114 | 115 | 116 | 117 | org.hibernate 118 | hibernate-entitymanager 119 | 120 | 121 | 122 | org.hibernate 123 | hibernate-validator 124 | 125 | 126 | 127 | org.hibernate.javax.persistence 128 | hibernate-jpa-2.0-api 129 | 130 | 131 | 132 | 133 | 134 | 135 | mysql 136 | mysql-connector-java 137 | 138 | 139 | 140 | com.h2database 141 | h2 142 | 143 | 144 | 145 | 146 | 147 | org.hamcrest 148 | hamcrest-core 149 | ${hamcrest.version} 150 | 151 | 152 | 153 | org.springframework.boot 154 | spring-boot-starter-test 155 | test 156 | 157 | 158 | 159 | com.jayway.jsonpath 160 | json-path 161 | 0.9.1 162 | test 163 | 164 | 165 | 166 | com.jayway.restassured 167 | rest-assured 168 | 2.3.1 169 | test 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /src/test/java/com/jayway/application/RestAssuredBankApplicationIT.java: -------------------------------------------------------------------------------- 1 | package com.jayway.application; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.jayway.config.SpringBootRunner; 6 | import org.apache.http.HttpHeaders; 7 | import org.apache.http.HttpStatus; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.IntegrationTest; 13 | import org.springframework.boot.test.SpringApplicationConfiguration; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.jdbc.core.JdbcTemplate; 16 | import org.springframework.test.context.ActiveProfiles; 17 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 18 | import org.springframework.test.context.web.WebAppConfiguration; 19 | 20 | import javax.sql.DataSource; 21 | import java.util.Collections; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | import static com.jayway.restassured.RestAssured.baseURI; 26 | import static com.jayway.restassured.RestAssured.given; 27 | import static org.hamcrest.Matchers.*; 28 | 29 | @RunWith(SpringJUnit4ClassRunner.class) 30 | @ActiveProfiles("mysql") 31 | @WebAppConfiguration 32 | @IntegrationTest 33 | @SpringApplicationConfiguration(classes = SpringBootRunner.class) 34 | public class RestAssuredBankApplicationIT { 35 | 36 | @Autowired 37 | DataSource dataSource; 38 | 39 | 40 | @Before 41 | public void setUp() { 42 | JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); 43 | jdbcTemplate.execute("TRUNCATE TABLE account_t"); 44 | jdbcTemplate.execute("INSERT INTO account_t (account_number, balance) VALUES (1, 100)"); 45 | jdbcTemplate.execute("INSERT INTO account_t (account_number, balance) VALUES (2, 200)"); 46 | } 47 | 48 | 49 | @Test 50 | public void shouldLog() { 51 | given(). 52 | auth(). 53 | basic("user", "secret"). 54 | log().all(). 55 | when(). 56 | get("/accounts/1"). 57 | then(). 58 | log().all(); 59 | } 60 | 61 | 62 | @Test 63 | public void shouldGetSingleAccount() { 64 | given(). 65 | auth(). 66 | basic("user", "secret"). 67 | when(). 68 | get("/accounts/1"). 69 | then(). 70 | statusCode(HttpStatus.SC_OK). 71 | contentType(MediaType.APPLICATION_JSON_VALUE). 72 | body("accountNumber", is(1)). 73 | body("balance", is(100)); 74 | } 75 | 76 | 77 | @Test 78 | public void shouldDeleteAccount() { 79 | given(). 80 | auth(). 81 | basic("user", "secret"). 82 | when(). 83 | delete("/accounts/1"). 84 | then(). 85 | statusCode(HttpStatus.SC_NO_CONTENT); 86 | } 87 | 88 | 89 | @Test 90 | public void shouldDepositToAccount() { 91 | Map body = Collections.singletonMap("amount", 10); 92 | String json = toJsonString(body); 93 | 94 | given(). 95 | auth(). 96 | basic("user", "secret"). 97 | request(). 98 | contentType(MediaType.APPLICATION_JSON_VALUE). 99 | body(json). 100 | when(). 101 | post("/accounts/1/deposit"). 102 | then(). 103 | statusCode(HttpStatus.SC_NO_CONTENT); 104 | } 105 | 106 | 107 | @Test 108 | public void shouldNotDepositNegativeAmount() { 109 | Map body = Collections.singletonMap("amount", -10); 110 | String json = toJsonString(body); 111 | 112 | given(). 113 | auth(). 114 | basic("user", "secret"). 115 | request(). 116 | contentType(MediaType.APPLICATION_JSON_VALUE). 117 | body(json). 118 | when(). 119 | post("/accounts/1/deposit"). 120 | then(). 121 | statusCode(HttpStatus.SC_BAD_REQUEST); 122 | } 123 | 124 | 125 | @Test 126 | public void shouldWithdrawFromAccount() { 127 | Map body = Collections.singletonMap("amount", 10); 128 | String json = toJsonString(body); 129 | 130 | given(). 131 | auth(). 132 | basic("user", "secret"). 133 | request(). 134 | contentType(MediaType.APPLICATION_JSON_VALUE). 135 | body(json). 136 | when(). 137 | post("/accounts/1/withdraw"). 138 | then(). 139 | statusCode(HttpStatus.SC_OK). 140 | body("accountNumber", is(1)). 141 | body("balance", is(90)); 142 | } 143 | 144 | 145 | @Test 146 | public void shouldNotOverdraw() { 147 | Map body = Collections.singletonMap("amount", 200); 148 | String json = toJsonString(body); 149 | 150 | given(). 151 | auth(). 152 | basic("user", "secret"). 153 | request(). 154 | contentType(MediaType.APPLICATION_JSON_VALUE). 155 | body(json). 156 | when(). 157 | post("/accounts/1/withdraw"). 158 | then(). 159 | statusCode(HttpStatus.SC_CONFLICT); 160 | } 161 | 162 | 163 | @Test 164 | public void shouldGetAccounts() { 165 | given(). 166 | auth(). 167 | basic("user", "secret"). 168 | when(). 169 | get("/accounts"). 170 | then(). 171 | statusCode(HttpStatus.SC_OK). 172 | contentType(MediaType.APPLICATION_JSON_VALUE). 173 | body("size()", is(2)). 174 | body("findAll {it}", hasItems(1, 2)); 175 | } 176 | 177 | 178 | @Test 179 | public void shouldCreateAccount() { 180 | given(). 181 | auth(). 182 | basic("user", "secret"). 183 | when(). 184 | post("/accounts"). 185 | then(). 186 | statusCode(HttpStatus.SC_CREATED). 187 | header(HttpHeaders.LOCATION, startsWith(baseURI)). 188 | header(HttpHeaders.LOCATION, containsString("/accounts/")); 189 | } 190 | 191 | 192 | @Test 193 | public void shouldNotGetUnknownAccount() { 194 | given(). 195 | auth(). 196 | basic("user", "secret"). 197 | when(). 198 | get("/accounts/0"). 199 | then(). 200 | statusCode(HttpStatus.SC_NOT_FOUND); 201 | } 202 | 203 | 204 | @Test 205 | public void shouldTransfer() { 206 | Map body = new HashMap(){{ 207 | put("fromAccountNumber", 1); 208 | put("toAccountNumber", 2); 209 | put("amount", 50); 210 | }}; 211 | String json = toJsonString(body); 212 | 213 | // transfer 214 | given(). 215 | auth(). 216 | basic("user", "secret"). 217 | request(). 218 | contentType(MediaType.APPLICATION_JSON_VALUE). 219 | body(json). 220 | when(). 221 | post("/transfer"). 222 | then(). 223 | statusCode(HttpStatus.SC_NO_CONTENT); 224 | 225 | // verify first account 226 | given(). 227 | auth(). 228 | basic("user", "secret"). 229 | when(). 230 | get("/accounts/1"). 231 | then(). 232 | statusCode(HttpStatus.SC_OK). 233 | contentType(MediaType.APPLICATION_JSON_VALUE). 234 | body("accountNumber", is(1)). 235 | body("balance", is(50)); 236 | 237 | // verify second account 238 | given(). 239 | auth(). 240 | basic("user", "secret"). 241 | when(). 242 | get("/accounts/2"). 243 | then(). 244 | statusCode(HttpStatus.SC_OK). 245 | contentType(MediaType.APPLICATION_JSON_VALUE). 246 | body("accountNumber", is(2)). 247 | body("balance", is(250)); 248 | } 249 | 250 | 251 | @Test 252 | public void shouldNotOverdrawDuringTransfer() { 253 | Map body = new HashMap(){{ 254 | put("fromAccountNumber", 1); 255 | put("toAccountNumber", 2); 256 | put("amount", 300); 257 | }}; 258 | String json = toJsonString(body); 259 | 260 | given(). 261 | auth(). 262 | basic("user", "secret"). 263 | request(). 264 | contentType(MediaType.APPLICATION_JSON_VALUE). 265 | body(json). 266 | when(). 267 | post("/transfer"). 268 | then(). 269 | statusCode(HttpStatus.SC_CONFLICT); 270 | } 271 | 272 | 273 | private static String toJsonString(Map map) { 274 | ObjectMapper objectMapper = new ObjectMapper(); 275 | try { 276 | return objectMapper.writeValueAsString(map); 277 | } catch (JsonProcessingException e) { 278 | throw new RuntimeException(e); 279 | } 280 | } 281 | 282 | } -------------------------------------------------------------------------------- /src/test/java/com/jayway/application/RestTemplateBankApplicationIT.java: -------------------------------------------------------------------------------- 1 | package com.jayway.application; 2 | 3 | import com.jayway.config.SpringBootRunner; 4 | import java.net.URI; 5 | import java.util.Collections; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import javax.sql.DataSource; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.IntegrationTest; 15 | import org.springframework.boot.test.SpringApplicationConfiguration; 16 | import org.springframework.boot.test.TestRestTemplate; 17 | import org.springframework.http.HttpEntity; 18 | import org.springframework.http.HttpHeaders; 19 | import org.springframework.http.HttpMethod; 20 | import org.springframework.http.HttpStatus; 21 | import org.springframework.http.MediaType; 22 | import org.springframework.http.ResponseEntity; 23 | import org.springframework.jdbc.core.JdbcTemplate; 24 | import org.springframework.test.context.ActiveProfiles; 25 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 26 | import org.springframework.test.context.web.WebAppConfiguration; 27 | import org.springframework.web.client.RestTemplate; 28 | 29 | import static org.hamcrest.Matchers.*; 30 | import static org.junit.Assert.assertThat; 31 | 32 | @RunWith(SpringJUnit4ClassRunner.class) 33 | @ActiveProfiles("mysql") 34 | @WebAppConfiguration 35 | @IntegrationTest 36 | @SpringApplicationConfiguration(classes = SpringBootRunner.class) 37 | public class RestTemplateBankApplicationIT { 38 | 39 | @Autowired 40 | DataSource dataSource; 41 | 42 | RestTemplate restTemplate; 43 | 44 | 45 | @Before 46 | public void setUp() { 47 | restTemplate = new TestRestTemplate("user", "secret"); 48 | 49 | JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); 50 | jdbcTemplate.execute("TRUNCATE TABLE account_t"); 51 | jdbcTemplate.execute("INSERT INTO account_t (account_number, balance) VALUES (1, 100)"); 52 | jdbcTemplate.execute("INSERT INTO account_t (account_number, balance) VALUES (2, 200)"); 53 | } 54 | 55 | 56 | @Test 57 | public void shouldGetAccount() throws Exception { 58 | ResponseEntity responseEntity = restTemplate 59 | .getForEntity("http://localhost:8080/accounts/{accountNumber}", Map.class, 1); 60 | 61 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK)); 62 | assertThat(responseEntity.getHeaders().getContentType(), is(MediaType.APPLICATION_JSON)); 63 | 64 | Map account = responseEntity.getBody(); 65 | assertThat(account.get("accountNumber"), is(1)); 66 | assertThat(account.get("balance"), is(100)); 67 | } 68 | 69 | 70 | @Test 71 | public void shouldDeleteAccount() throws Exception { 72 | ResponseEntity responseEntity = restTemplate 73 | .exchange("http://localhost:8080/accounts/{accountNumber}", 74 | HttpMethod.DELETE, null, Map.class, 1); 75 | 76 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.NO_CONTENT)); 77 | assertThat(responseEntity.getBody(), nullValue()); 78 | } 79 | 80 | 81 | @Test 82 | public void shouldDepositToAccount() throws Exception { 83 | // create payload 84 | Map body = Collections.singletonMap("amount", 50); 85 | HttpHeaders headers = new HttpHeaders(); 86 | headers.setContentType(MediaType.APPLICATION_JSON); 87 | HttpEntity> entity = new HttpEntity<>(body, headers); 88 | 89 | // deposit 90 | ResponseEntity responseEntity = restTemplate 91 | .postForEntity("http://localhost:8080/accounts/{accountNumber}/deposit", 92 | entity, Map.class, 1); 93 | 94 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.NO_CONTENT)); 95 | assertThat(responseEntity.getBody(), nullValue()); 96 | } 97 | 98 | 99 | @Test 100 | public void shouldNotDepositNegativeAmount() { 101 | // create payload 102 | Map body = Collections.singletonMap("amount", -10); 103 | HttpHeaders headers = new HttpHeaders(); 104 | headers.setContentType(MediaType.APPLICATION_JSON); 105 | HttpEntity> entity = new HttpEntity<>(body, headers); 106 | 107 | // deposit 108 | ResponseEntity responseEntity = restTemplate 109 | .postForEntity("http://localhost:8080/accounts/{accountNumber}/deposit", 110 | entity, Void.class, 1); 111 | 112 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.BAD_REQUEST)); 113 | } 114 | 115 | 116 | @Test 117 | public void shouldWithdrawFromAccount() { 118 | // create payload 119 | Map body = Collections.singletonMap("amount", 10); 120 | HttpHeaders headers = new HttpHeaders(); 121 | headers.setContentType(MediaType.APPLICATION_JSON); 122 | HttpEntity> entity = new HttpEntity<>(body, headers); 123 | 124 | // deposit 125 | ResponseEntity responseEntity = restTemplate 126 | .postForEntity("http://localhost:8080/accounts/{accountNumber}/withdraw", 127 | entity, Map.class, 1); 128 | 129 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK)); 130 | Map account = responseEntity.getBody(); 131 | assertThat(account.get("accountNumber"), is(1)); 132 | assertThat(account.get("balance"), is(90)); 133 | } 134 | 135 | 136 | @Test 137 | public void shouldNotOverdraw() { 138 | // create payload 139 | Map body = Collections.singletonMap("amount", 200); 140 | HttpHeaders headers = new HttpHeaders(); 141 | headers.setContentType(MediaType.APPLICATION_JSON); 142 | HttpEntity> entity = new HttpEntity<>(body, headers); 143 | 144 | // withdraw 145 | ResponseEntity responseEntity = restTemplate 146 | .postForEntity("http://localhost:8080/accounts/{accountNumber}/withdraw", 147 | entity, Map.class, 1); 148 | 149 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.CONFLICT)); 150 | } 151 | 152 | 153 | @Test 154 | public void shouldGetAccounts() { 155 | ResponseEntity responseEntity = restTemplate 156 | .getForEntity("http://localhost:8080/accounts", List.class); 157 | 158 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK)); 159 | List accountNumbers = responseEntity.getBody(); 160 | assertThat(accountNumbers.size(), is(2)); 161 | assertThat(accountNumbers, contains(1, 2)); 162 | } 163 | 164 | 165 | @Test 166 | public void shouldCreateAccount() { 167 | ResponseEntity responseEntity = restTemplate 168 | .postForEntity("http://localhost:8080/accounts", null, Map.class); 169 | 170 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.CREATED)); 171 | HttpHeaders headers = responseEntity.getHeaders(); 172 | URI location = headers.getLocation(); 173 | assertThat(location.toString(), startsWith("http://localhost:8080/accounts/")); 174 | } 175 | 176 | 177 | @Test 178 | public void shouldNotGetUnknownAccount() { 179 | ResponseEntity responseEntity = restTemplate 180 | .getForEntity("http://localhost:8080/accounts/{accountNumber}", Void.class, 0); 181 | 182 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.NOT_FOUND)); 183 | } 184 | 185 | 186 | @Test 187 | public void shouldTransfer() { 188 | // create payload 189 | Map body = new HashMap(){{ 190 | put("fromAccountNumber", 1); 191 | put("toAccountNumber", 2); 192 | put("amount", 50); 193 | }}; 194 | HttpHeaders headers = new HttpHeaders(); 195 | headers.setContentType(MediaType.APPLICATION_JSON); 196 | HttpEntity> entity = new HttpEntity<>(body, headers); 197 | 198 | // transfer 199 | ResponseEntity responseEntity = restTemplate.postForEntity("http://localhost:8080/transfer", entity, Map.class); 200 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.NO_CONTENT)); 201 | assertThat(responseEntity.getBody(), nullValue()); 202 | 203 | // verify first account 204 | responseEntity = restTemplate 205 | .getForEntity("http://localhost:8080/accounts/{accountNumber}", Map.class, 1); 206 | Map account = responseEntity.getBody(); 207 | assertThat(account.get("accountNumber"), is(1)); 208 | assertThat(account.get("balance"), is(50)); 209 | 210 | // verify second account 211 | responseEntity = restTemplate 212 | .getForEntity("http://localhost:8080/accounts/{accountNumber}", Map.class, 2); 213 | account = responseEntity.getBody(); 214 | assertThat(account.get("accountNumber"), is(2)); 215 | assertThat(account.get("balance"), is(250)); 216 | } 217 | 218 | 219 | @Test 220 | public void shouldNotOverdrawDuringTransfer() { 221 | // create payload 222 | Map body = new HashMap(){{ 223 | put("fromAccountNumber", 1); 224 | put("toAccountNumber", 2); 225 | put("amount", 300); 226 | }}; 227 | HttpHeaders headers = new HttpHeaders(); 228 | headers.setContentType(MediaType.APPLICATION_JSON); 229 | HttpEntity> entity = new HttpEntity<>(body, headers); 230 | 231 | // transfer 232 | ResponseEntity responseEntity = restTemplate.postForEntity("http://localhost:8080/transfer", entity, Map.class); 233 | assertThat(responseEntity.getStatusCode(), is(HttpStatus.CONFLICT)); 234 | assertThat(responseEntity.getBody(), nullValue()); 235 | } 236 | 237 | } 238 | --------------------------------------------------------------------------------