Beginning Spring学习笔记——第7章 使用Spring进行测试驱动开发

配置和缓存ApplicationContext


测试中使用基于XML和Java的上下文配置

首先创建maven项目,依赖配置如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.lonelyquantum.springbeginning.wileybookch4</groupId>
  <artifactId>JDBCTest</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>JDBCTest</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.3.10.RELEASE</spring.version>
    <junit.version>4.12</junit.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>
  </dependencies>
</project>

在src/main/java下创建com.wiley.beginningspring.ch7包,创建第二章创建的一系列类和接口。
Account类:

public class Account {
    private long id;
    private String ownerName;
    private double balance;
    private Date accessTime;
    private boolean locked;
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getOwnerName() {
        return ownerName;
    }
    public void setOwnerName(String ownerName) {
        this.ownerName = ownerName;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
    public Date getAccessTime() {
        return accessTime;
    }
    public void setAccessTime(Date accessTime) {
        this.accessTime = accessTime;
    }
    public boolean isLocked() {
        return locked;
    }
    public void setLocked(boolean locked) `
        this.locked = locked;
    }
}

AccountDao接口和其实现类AccountDaoInMemoryImpl:

public interface AccountDao {
    public void insert(Account account);
    public void update(Account account);
    public void update(List<Account> accounts);
    public void delete(long accountId);
    public Account find(long accountId);
    public List<Account> find(List<Long> accountIds);
    public List<Account> find(String ownerName);
    public List<Account> find(boolean locked);
}

public class AccountDaoInMemoryImpl implements AccountDao {

    private Map<Long,Account> accountsMap = new HashMap<>();

    public void setAccountsMap(Map<Long, Account> accountsMap) {
        this.accountsMap = accountsMap;
    }

    {
        Account account1 = new Account();
        account1.setId(1L);
        account1.setOwnerName("John");
        account1.setBalance(10.0);

        Account account2 = new Account();
        account2.setId(2L);
        account2.setOwnerName("Mary");
        account2.setBalance(20.0);

        accountsMap.put(account1.getId(), account1);
        accountsMap.put(account2.getId(), account2);

    }

    @Override
    public void insert(Account account) {
        accountsMap.put(account.getId(), account);
    }

    @Override
    public void update(Account account) {
        accountsMap.put(account.getId(), account);
    }

    @Override
    public void update(List<Account> accounts) {
        for(Account account:accounts) {
            update(account);
        }
    }

    @Override
    public void delete(long accountId) {
        accountsMap.remove(accountId);
    }

    @Override
    public Account find(long accountId) {
        return accountsMap.get(accountId);
    }

    @Override
    public List<Account> find(List<Long> accountIds) {
        List<Account> accounts = new ArrayList<>();
        for(Long id:accountIds) {
            accounts.add(accountsMap.get(id));
        }
        return accounts;
    }

    @Override
    public List<Account> find(String ownerName) {
        List<Account> accounts = new ArrayList<>();
        for(Account account:accountsMap.values()) {
            if(ownerName.equals(account.getOwnerName())) {
                accounts.add(account);
            }
        }
        return accounts;
    }

    @Override
    public List<Account> find(boolean locked) {
        List<Account> accounts = new ArrayList<>();
        for(Account account:accountsMap.values()) {
            if(locked == account.isLocked()) {
                accounts.add(account);
            }
        }
        return accounts;
    }

}

AccountService接口和其实现类AccountServiceImpl。

public interface AccountService {
    public void transferMoney(long sourceAccountId, long targetAccountId, double amount);
    public void depositMoney(long accountId, double amount) throws Exception;
    public Account getAccount(long accountId);
}

public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public void transferMoney(long sourceAccountId, long targetAccountId, double amount) {
        Account sourceAccount = accountDao.find(sourceAccountId);
        Account targetAccount = accountDao.find(targetAccountId);
        sourceAccount.setBalance(sourceAccount.getBalance() - amount);
        targetAccount.setBalance(targetAccount.getBalance() + amount);
        accountDao.update(sourceAccount);
        accountDao.update(targetAccount);
    }

    @Override
    public void depositMoney(long accountId, double amount) throws Exception {
        Account account = accountDao.find(accountId);
        account.setBalance(account.getBalance() + amount);
        accountDao.update(account);
    }

    @Override
    public Account getAccount(long accountId) {
        return accountDao.find(accountId);
    }   
}

采用XML配置时,在src/main/resource下创建applicationContext.xml Bean配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountService" class="com.wiley.beginningspring.ch7.AccountServiceImpl">
        <property name="accountDao" ref="accountDao" />
    </bean>

    <bean id="accountDao" class="com.wiley.beginningspring.ch7.AccountDaoInMemoryImpl"/>

</beans>

此时在src/test/java文件夹中创建com.wiley.beginningspring.ch7包并创建测试XML配置的测试类AccountIntegrationTests。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/applicationContext.xml")
public class AccountIntegrationTests {

    @Autowired
    private AccountService accountService;

    @Test
    public void accountServiceShouldBeInjected() {
        Assert.assertNotNull(accountService);
    }
}

作为JUnit测试运行通过,说明配置成功。
此外,还可以使用Java文件配置,此时不需要创建以上XML文件,而是在类包内创建配置类Ch7Configuration。

@Configuration
public class Ch7Configuration {
    @Bean
    public AccountService accountService() {
        AccountServiceImpl bean = new AccountServiceImpl();
        bean.setAccountDao(accountDao());
        return bean;
    }

    @Bean
    public AccountDao accountDao() {
        AccountDaoInMemoryImpl bean = new AccountDaoInMemoryImpl();
        //depedencies of accountDao bean will be injected here...
        return bean;
    }
}

此时在测试包中创建的文件变为:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={Ch7Configuration.class})
public class AccountIntegrationTestsWithJavaConfig {
    @Autowired
    private AccountService accountService;

    @Test
    public void accountServiceShouldBeInjected() {
        Assert.assertNotNull(accountService);
    }
}

当然,也可以两种配置一起使用,此时测试类变为:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={Ch7Configuration.class,Config.class})
public class AccountIntegrationTestsWithMixedConfig {

    @Configuration
    @ImportResource("classpath:/applicationContext.xml")
    static class Config {
    }


    @Autowired
    private AccountService accountService;

    @Test
    public void accountServiceShouldBeInjected() {
        Assert.assertNotNull(accountService);
    }
}

此外还可以使用ApplicationContextInitializer来配置上下文,这时,先在一个TestInitializer类中完成配置文件的导入和上下文的预初始化:

public class TestInitializer implements ApplicationContextInitializer<GenericApplicationContext> {

    @Override
    public void initialize(GenericApplicationContext applicationContext) {
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(applicationContext);
        reader.loadBeanDefinitions("classpath:/applicationContext.xml");
    }

}

然后再利用该上下文进行测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(initializers={TestInitializer.class})
public class AccountIntegrationTestsWithInitializer {
    @Autowired
    private AccountService accountService;

    @Test
    public void accountServiceShouldBeInjected() {
        Assert.assertNotNull(accountService);
    }
}

以上测试目录结构为:
目录结构

继承上下文配置

继承于中间类或者基础类的类可以继承测试相关配置。
先展示目录结构:
目录结构
首先创建如下两个类:

public class Bar {

}

public class Foo {

}

然后创建基础类的配置文件baseContext.xml。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="foo" class="com.wiley.beginningspring.ch7.Foo"/>

</beans>

接着创建子配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bar" class="com.wiley.beginningspring.ch7.Bar"/>

</beans>

然后创建基础测试类和孩子测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/baseContext.xml")
public class BaseTest {
    @Autowired
    protected Foo foo;
}

@ContextConfiguration("classpath:/subContext.xml")
public class ChildTest extends BaseTest {
    @Autowired
    private Bar bar;

    @Test
    public void dependenciesShouldBeAvailable() {
        Assert.assertNotNull(foo);
        Assert.assertNotNull(bar);
    }
}

运行孩子测试类通过,说明孩子测试类继承了基础测试类的配置文件。

ApplicationContext缓存

如果多个测试类指定了完全相同的XML位置和配置类,那么Spring TestContext Framework将只创建一次ApplicatoinContext实例,并在运行这些测试类之间共享该实例。
缓存被保存再一个静态变量中。

注入测试夹具的依赖项


项目目录结构如下:
目录结构
首先创建如下两个类:

public class Bar {

}

public class Foo {

}

然后创建测试类:

@Configuration
public class Ch7ConfigurationForDependencyInjection {
    @Bean
    public Foo foo1() {
        return new Foo();
    }

    @Bean
    public Foo foo2() {
        return new Foo();
    }

    @Bean
    public Bar bar1() {
        return new Bar();
    }
}

最后创建测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=Ch7ConfigurationForDependencyInjection.class)
public class DependencyInjectionTests {

    @Autowired
    @Qualifier("foo1")
    private Foo foo1;

    @Resource
    private Foo foo2;

    @Resource
    private Bar bar;

    @Test
    public void testInjections() {
        Assert.assertNotNull(foo1);
        Assert.assertNotNull(foo2);
        Assert.assertNotNull(bar);
    }

}

运行通过测试,可见配置类中的依赖被成功注入到测试类中。

在测试中使用事务管理


在测试方法或者测试类级别使用@Transactional注解即可作为事务测试,通常测试后会进行回滚操作,如果不想回滚而是想提交事务,则需要另外设置不回滚。

@Test
@Transactional
@Rollback(false)
public void transactionalTestMethod(){
}

此外,使用ORM框架,如Hibernate或JPA时,要在测试方法结束时刷新当前Hibernate Session或者JPA EntityManager,否则由于测试结束后进行回滚,其上累积的持久化操作不会提交,SQL操作不会执行,导致无法检查由数据库交互失败的错误。

测试Web应用程序


此时需要支持web的依赖文件:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.lonelyquantum.springbeginning.wileybookch4</groupId>
  <artifactId>JDBCTest</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>JDBCTest</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.3.10.RELEASE</spring.version>
    <junit.version>4.12</junit.version>
    <javax.servlet.version>3.1.0</javax.servlet.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>${javax.servlet.version}</version>
    </dependency>
  </dependencies>
</project>

测试的目录结构为:
目录结构
首先创建空的XML配置文件applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

然后创建如下测类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/applicationContext.xml")
@WebAppConfiguration
public class WebApplicationTests {

    @Autowired
    private WebApplicationContext applicationContext;

    @Autowired
    private MockServletContext servletContext;

    @Autowired
    private MockHttpServletRequest httpServletRequest;

    @Autowired
    private MockHttpServletResponse httpServletResponse;

    @Test
    public void testWebApp() {
        Assert.assertNotNull(applicationContext);
        Assert.assertNotNull(servletContext);

        Assert.assertNotNull(httpServletRequest);
        Assert.assertNotNull(httpServletResponse);
    }

}

注意到类上使用了@WebAppConfiguration表示为Web项目。运行测试成功通过。

测试Request和Session作用域的Bean


依赖文件不变,目录结构如下:
目录结构
首先创建如下三个类:

public class LoginAction {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

public class UserPreferences {
    private String theme;

    public String getTheme() {
        return theme;
    }

    public void setTheme(String theme) {
        this.theme = theme;
    }
}

public class UserService {
    private LoginAction loginAction;
    private UserPreferences userPreferences;
    public LoginAction getLoginAction() {
        return loginAction;
    }
    public void setLoginAction(LoginAction loginAction) {
        this.loginAction = loginAction;
    }
    public UserPreferences getUserPreferences() {
        return userPreferences;
    }
    public void setUserPreferences(UserPreferences userPreferences) {
        this.userPreferences = userPreferences;
    }
}

用户操作和用户偏好为两个域类,而用户服务类中设置这两个域类实例。
在配置文件applicationContext.xml中用SpEL定义了他们的Bean并注入了依赖:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="loginAction" class="com.wiley.beginningspring.ch7.LoginAction" scope="request">
        <property name="username" value="#{request.getParameter('username')}"/>
        <property name="password" value="#{request.getParameter('password')}"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="userPreferences" class="com.wiley.beginningspring.ch7.UserPreferences" scope="session">
        <property name="theme" value="#{session.getAttribute('theme')}"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="userService" class="com.wiley.beginningspring.ch7.UserService">
        <property name="loginAction" ref="loginAction"/>
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

然后子测试类中验证了注入的依赖:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("classpath:/applicationContext.xml")
public class ScopedBeanTests {
    @Autowired
    private UserService userService;

    @Autowired
    private MockHttpServletRequest httpServletRequest;

    @Autowired
    private MockHttpSession httpSession;

    @Test
    public void testScopedBeans() {
        httpServletRequest.setParameter("username", "jdoe");
        httpServletRequest.setParameter("password", "secret");

        httpSession.setAttribute("theme", "blue");

        Assert.assertEquals("jdoe",userService.getLoginAction().getUsername());
        Assert.assertEquals("secret", userService.getLoginAction().getPassword());
        Assert.assertEquals("blue", httpSession.getAttribute("theme"));
    }
}

测试Spring MVC项目


该项目需要的maven依赖如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.springframework.samples.service.service</groupId>
  <artifactId>SpringAOPTest</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>

    <properties>

        <!-- Generic properties -->
        <java.version>1.6</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <!-- Web -->
        <jsp.version>2.3.1</jsp.version>
        <jstl.version>1.2</jstl.version>
        <servlet.version>3.1.0</servlet.version>


        <!-- Spring -->
        <spring-framework.version>4.3.10.RELEASE</spring-framework.version>

        <!-- Hibernate / JPA -->
        <hibernate.version>5.2.10.Final</hibernate.version>

        <!-- Logging -->
        <logback.version>1.2.3</logback.version>
        <slf4j.version>1.7.25</slf4j.version>

        <!-- Test -->
        <junit.version>4.12</junit.version>

        <!-- AspectJ -->
        <aspectj.version>1.8.10</aspectj.version>

        <!-- JSON Evaluation -->
        <jackson.version>2.9.0</jackson.version>

        <!-- Validator -->
        <hibernate-validator.version>6.0.2.Final</hibernate-validator.version>
        <javax-validation.version>2.0.0.Final</javax-validation.version>


    </properties>

    <dependencies>

        <!-- Spring MVC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <!-- Other Web dependencies -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>${jstl.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>${jsp.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- Spring and Transactions -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <!-- Logging with SLF4J & LogBack -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
            <scope>runtime</scope>
        </dependency>

        <!-- Hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>


        <!-- Test Artifacts -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring-framework.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- AspectJ -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
        </dependency>

        <!-- JSON Evaluation -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <!-- Validator -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>${hibernate-validator.version}</version>
        </dependency>

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>${javax-validation.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.hamcrest/hamcrest-all -->
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-all</artifactId>
            <version>1.3</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.el/javax.el-api -->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>3.0.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.glassfish/javax.el -->
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.el</artifactId>
            <version>3.0.0</version>
        </dependency>
    <dependencies>  
</project>

项目目录结构如下:
目录结构
首先创建项目所需要的User和simpleUser域类。

public class User {

    @Size(min=3, max=20)
    String username;
    @Email
    String email;
    @CreditCardNumber
    String ccNumber;
    @Pattern(regexp = "^[a-zA-Z]\\w{3,14}$")
    String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getCcNumber() {
        return ccNumber;
    }

    public void setCcNumber(String ccNumber) {
        this.ccNumber = ccNumber;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

public class SimpleUser {

    private String name;
    private String lastName;

    public SimpleUser() {
    }

    public SimpleUser(String name, String lastName) {
        this.name = name;
        this.lastName = lastName;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

然后创造查找失败抛出的异常类。

public class UserNotFoundException extends Exception {

    public UserNotFoundException(String name) {
        super("User not found with name: " + name);
    }
}

然后创建一个简单的控制器。

@Controller
public class HelloReaderController {

    @RequestMapping(value = "/hello")
    public ModelAndView sayHello() {
        ModelAndView mv = new ModelAndView();
        mv.addObject("message", "Hello Reader!");
        mv.setViewName("helloReader");

        return mv;
    }
}

为方便测试,创建一个加载上下文的基类,之后的所有测试类都继承该类从而继承上下文配置。

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/springmvc-servlet.xml")
public abstract class BaseControllerTests {
}

其中上下文配置文件如下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd 
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/mvc 
                           http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.lonelyquantum.wileybookch7" />
    <context:annotation-config />
    <mvc:annotation-driven validator="validator" />

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
</beans>

创建测试简单控制器的测试类。

public class HelloReaderControllerTests extends BaseControllerTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    @Test
    public void helloReaderControllerWorksOk() throws Exception {
        mockMvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andDo(print())
                .andExpect(model().attribute("message", "Hello Reader!"))
                .andExpect(view().name("helloReader"));
    }

    @Test
    public void helloReaderControllerWorksOkWithAnUnmappedUrl() throws Exception {
        mockMvc.perform(post("/helloMyLove"))
                .andExpect(status().isNotFound());
    }
}

测试中使用MockMvc类来进行控制器测试。可以看见该类可以发送http请求并测试返回值是否符合预期。测试通过。
然后创建表单提交控制类。

@Controller
public class UserController {

    @RequestMapping(value = "/result")
    public ModelAndView processUser(@Valid User user, BindingResult result) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("u", user);

        if (result.hasErrors()) {
            modelAndView.setViewName("userForm");
        }
        else {
            modelAndView.setViewName("userResult");
        }

        return modelAndView;
    }
}

和它的测试类。

public class UserControllerTests extends BaseControllerTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    @Test
    public void formSubmittedSuccessfully() throws Exception {
        this.mockMvc.perform(
                post("/result")
                        .param("username", "johndoe")
                        .param("email", "john@doe.com")
                        .param("ccNumber", "5245771326014172")
                        .param("password", "TestR0ck"))
                .andExpect(status().isOk())
                .andExpect(view().name("userResult"))
                .andExpect(model().hasNoErrors())
                .andExpect(model().attribute("u", hasProperty("username", is("johndoe"))))
                .andExpect(model().attribute("u", hasProperty("email", is("john@doe.com"))))
                .andExpect(model().attribute("u", hasProperty("ccNumber", is("5245771326014172"))))
                .andExpect(model().attribute("u", hasProperty("password", is("TestR0ck"))));
    }

    @Test
    public void formSubmittedSuccessfullyButContainsValidationErrors() throws Exception {
        this.mockMvc.perform(
                post("/result")
                        .param("username", "ok"))
                .andExpect(status().isOk())
                .andExpect(view().name("userForm"))
                .andExpect(model().hasErrors());
    }
}

该测试类中通过param加入了输入变量作为提交表单的内容,然后用同样的方法测试返回值。测试通过。
最后创建异常处理类。

@Controller
public class User2Controller {

    private Map<String, SimpleUser> users = new HashMap<String, SimpleUser>();

    @PostConstruct
    public void setup() {
        users.put("mert", new SimpleUser("Mert", "Caliskan"));
        users.put("kenan", new SimpleUser("Kenan", "Sevindik"));
    }

    @RequestMapping(value = "/findUser")
    public ModelAndView processUser(String name) throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        SimpleUser user = users.get(name);

        if (user == null) {
            throw new UserNotFoundException(name);
        }
        modelAndView.addObject("u", user);
        modelAndView.setViewName("userResult");

        return modelAndView;
    }

    @ExceptionHandler(UserNotFoundException.class)
    public ModelAndView handleException(UserNotFoundException e) {
        ModelAndView modelAndView = new ModelAndView("errorUser");
        modelAndView.addObject("errorMessage", e.getMessage());
        return modelAndView;
    }
}

和它的测试类。

public class User2ControllerTests extends BaseControllerTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    @Test
    public void userNotFoundExceptionHandledSuccessfully() throws Exception {
        this.mockMvc.perform(get("/findUser").param("name", "johndoe"))
                .andExpect(status().isOk())
                .andExpect(view().name("errorUser"))
                .andExpect(model().attribute("errorMessage", "User not found with name: johndoe"));
    }
}

测试通过。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值