射阳网站建设,开发一个视频app需要多少钱,哪里有培训网页设计,做网站精英集成spring mvc我已经写了关于为使用Spring Social 1.1.0的应用程序编写单元测试的挑战#xff0c;并为此提供了一种解决方案 。 尽管单元测试很有价值#xff0c;但它并不能真正告诉我们我们的应用程序是否正常运行。 这就是为什么我们必须为此编写集成测试的原因 。 这篇… 集成spring mvc 我已经写了关于为使用Spring Social 1.1.0的应用程序编写单元测试的挑战并为此提供了一种解决方案 。 尽管单元测试很有价值但它并不能真正告诉我们我们的应用程序是否正常运行。 这就是为什么我们必须为此编写集成测试的原因 。 这篇博客文章可以帮助我们做到这一点。 在此博客文章中我们将学习如何为示例应用程序的注册和登录功能编写集成测试。 如果您尚未阅读Spring Social教程的先前部分建议您在阅读本博客文章之前先阅读它们。 以下描述了此博客文章的前提条件 在Spring MVC Web应用程序中添加社交登录配置描述了如何配置示例应用程序。 在Spring MVC Web应用程序中添加社交登录注册和登录描述了如何向示例应用程序添加注册和登录功能。 在Spring MVC Web应用程序中添加社交登录单元测试描述了如何为示例应用程序编写单元测试。 Spring MVC测试教程描述了如何使用Spring MVC Test框架编写单元测试和集成测试。 Spring Data JPA教程集成测试描述了我们如何为Spring Data JPA存储库编写集成测试。 这篇博客文章帮助您了解如何使用Spring Test DBUnit和DbUnit编写集成测试。 使用Maven进行集成测试介绍了如何使用Maven运行集成测试和单元测试。 我们的示例应用程序的构建过程遵循此博客文章中描述的方法。 让我们从对构建过程的配置进行一些更改开始。 配置我们的构建过程 我们必须对构建过程的配置进行以下更改 我们已经配置了一个本地Maven存储库并将Spring Test DbUnit 1.1.1快照二进制文件添加到该存储库。 我们必须将所需的测试依赖项添加到我们的POM文件中。 我们必须将Liquibase变更集文件添加到classpath。 让我们找出如何进行这些更改。 将Spring Test DBUnit快照二进制文件添加到本地Maven存储库 由于Spring Test DBUnit的稳定版本与Spring Framework 4不兼容 因此我们必须在集成测试中使用构建快照。 我们可以按照以下步骤将Spring Test DBUnit快照添加到本地Maven存储库 从Github克隆Spring Test DBUnit存储库并创建快照二进制文件。 创建etc / mavenrepo目录。 该目录是我们本地的Maven存储库。 将创建的jar文件复制到etc / mavenrepo / com / github / springtestdbunit / 1.1.1-SNAPSHOT目录。 将jar文件复制到本地Maven存储库后我们必须在pom.xml文件中配置本地存储库的位置。 我们可以通过将以下存储库声明添加到我们的POM文件中来做到这一点 repositories!-- Other repositories are omitted for the sake of clarity --repositoryidlocal-repository/idnamelocal repository/nameurlfile://${project.basedir}/etc/mavenrepo/url/repository
/repositories使用Maven获取所需的测试依赖项 通过将以下依赖项声明添加到我们的POM文件中我们可以获得所需的测试依赖项 Spring测试DBUnit 版本1.1.1-SNAPSHOT。 我们使用Spring Test DBUnit将Spring Test框架与DbUnit库集成。 DbUnit 版本2.4.9。 在每次集成测试之前我们使用DbUnit将数据库初始化为已知状态并验证数据库的内容是否与预期数据匹配。 liquibase-core 版本3.1.1。 加载集成测试的应用程序上下文时我们使用Liquibase创建一些数据库表。 pom.xml文件的相关部分如下所示 dependencygroupIdcom.github.springtestdbunit/groupIdartifactIdspring-test-dbunit/artifactIdversion1.1.1-SNAPSHOT/versionscopetest/scope
/dependency
dependencygroupIdorg.dbunit/groupIdartifactIddbunit/artifactIdversion2.4.9/versionscopetest/scope
/dependency
dependencygroupIdorg.liquibase/groupIdartifactIdliquibase-core/artifactIdversion3.1.1/versionscopetest/scope
/dependency将Liquibase变更集添加到类路径 通常我们应该让Hibernate创建用于集成测试的数据库。 但是仅当在我们的域模型中配置了每个数据库表时此方法才有效。 现在不是这种情况。 示例应用程序的数据库具有一个UserConnection表该表未在示例应用程序的域模型中配置。 这就是为什么我们需要在运行集成测试之前找到另一种方法来创建UserConnection表。 为此我们可以使用Liquibase库的Spring集成但这意味着我们必须将Liquibase变更集添加到类路径中。 我们可以通过使用Build Helper Maven插件来实现 。 我们可以按照以下步骤将Liquibase变更集添加到类路径中 确保在generate-test-resources生命周期阶段调用了Builder Helper Maven插件的add-test-resource目标。 配置插件以将etc / db目录添加到类路径此目录包含所需的文件。 插件配置的相关部分如下所示 plugingroupIdorg.codehaus.mojo/groupIdartifactIdbuild-helper-maven-plugin/artifactIdversion1.7/versionexecutions!-- Other executions are omitted for the sake of clarity --executionidadd-integration-test-resources/id!-- Run this execution in the generate-test-sources lifecycle phase --phasegenerate-test-resources/phasegoals!-- Invoke the add-test-resource goal of this plugin --goaladd-test-resource/goal/goalsconfigurationresources!-- Other resources are omitted for the sake of clarity --!-- Add the directory which contains Liquibase change sets to classpath --resourcedirectoryetc/db/directory/resource/resources/configuration/execution/executions
/plugin 如果要获取有关使用Builder Helper Maven插件的用法的更多信息可以查看以下网页 与Maven的集成测试 Builder Helper Maven插件 现在我们已经完成了构建过程的配置。 让我们找出如何配置集成测试。 配置我们的集成测试 我们可以按照以下步骤配置集成测试 修改Liquibase更改日志文件。 在调用我们的测试用例之前配置应用程序上下文以运行Liquibase变更集。 创建一个自定义DbUnit数据集加载器。 配置集成测试用例 让我们继续前进仔细看看每个步骤。 修改Liquibase变更日志 我们的示例应用程序有两个Liquibase变更集可从etc / db / schema目录中找到。 这些变更集是 db-0.0.1.sql文件创建UserConnection表该表用于将用户与已使用的社交登录提供程序的连接持久化。 db-0.0.2.sql文件创建user_accounts表该表包含我们示例应用程序的用户帐户。 因为我们只想运行第一个变更集所以我们必须对Liquibase变更日志文件进行一些修改。 更具体地说我们必须使用Liquibase上下文来指定 当创建示例应用程序的数据库时将执行哪些变更集。 当我们运行集成测试时将执行哪些变更集。 通过执行以下步骤我们可以实现我们的目标 指定当Liquibase上下文是db或integrationtest时将执行db-0.0.1.sql changeset文件。 指定当Liquibase上下文为db时执行db-0.0.2.sql changeset文件。 我们的Liquibase changelog文件如下所示 ?xml version1.0 encodingUTF-8?
databaseChangeLogxmlnshttp://www.liquibase.org/xml/ns/dbchangelogxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd!-- Run this change set when the database is created and integration tests are run --changeSet id0.0.1 authorPetri contextdb,integrationtestsqlFile pathschema/db-0.0.1.sql //changeSet!-- Run this change set when the database is created --changeSet id0.0.2 authorPetri contextdbsqlFile pathschema/db-0.0.2.sql //changeSet
/databaseChangeLog在运行集成测试之前执行Liquibase变更集 通过在加载应用程序上下文时执行集成测试我们可以在运行集成测试之前执行Liquibase变更集。 我们可以按照以下步骤进行操作 创建一个IntegrationTestContext类并使用Configuration注释对其进行注释。 将DataSource字段添加到创建的类中并使用Autowired注释对其进行注释。 将liquibase方法添加到类中并使用Bean注释对其进行注释。 此方法配置SpringLiquibase bean该bean在加载应用程序上下文时执行liquibase变更集。 通过执行以下步骤来实现liquibase方法 创建一个新的SpringLiquibase对象。 配置创建的对象使用的数据源。 配置Liquibase更改日志的位置。 将Liquibase上下文设置为“ integrationtest”。 返回创建的对象。 IntegrationTestContext类的源代码如下所示 import liquibase.integration.spring.SpringLiquibase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.sql.DataSource;Configuration
public class IntegrationTestContext {Autowiredprivate DataSource dataSource;Beanpublic SpringLiquibase liquibase() {SpringLiquibase liquibase new SpringLiquibase();liquibase.setDataSource(dataSource);liquibase.setChangeLog(classpath:changelog.xml);liquibase.setContexts(integrationtest);return liquibase;}
}创建一个自定义DataSetLoader类 包含不同用户帐户信息的DbUnit数据集如下所示 ?xml version1.0 encodingUTF-8?
datasetuser_accounts id1creation_time2014-02-20 11:13:28emailfacebooksocialuser.comfirst_nameFacebooklast_nameUsermodification_time2014-02-20 11:13:28roleROLE_USERsign_in_providerFACEBOOKversion0/user_accounts id2creation_time2014-02-20 11:13:28emailtwittersocialuser.comfirst_nameTwitterlast_nameUsermodification_time2014-02-20 11:13:28roleROLE_USERsign_in_providerTWITTERversion0/user_accounts id3creation_time2014-02-20 11:13:28emailregistereduser.comfirst_nameRegisteredUserlast_nameUsermodification_time2014-02-20 11:13:28password$2a$10$PFSfOaC2IFPG.1HjO05KoODRVSdESQ5q7ek4IyzVfTf.VWlKDa/.eroleROLE_USERversion0/UserConnection/
/dataset 我们可以从该数据集中看到两件事 使用社交登录创建用户帐户的用户没有密码。 通过常规注册创建其用户帐户的用户具有密码但没有登录提供者。 这是一个问题因为我们使用所谓的平面XML数据集 并且默认的DbUnit数据集加载器无法处理这种情况 。 当然我们可以开始使用标准XML数据集但就我的口味而言其语法有点过于冗长。 这就是为什么我们必须创建一个可以处理这种情况的自定义数据集加载器的原因。 我们可以按照以下步骤创建自定义数据集加载器 创建一个ColumnSensingFlatXMLDataSetLoader类该类扩展了AbstractDataSetLoader类。 重写createDataSet方法并通过以下步骤实现它 创建一个新的FlatXmlDataSetBuilder对象。 启用列感测。 列检测意味着DbUnit从数据集文件中读取整个数据集并在从数据集中找到新列时添加新列。 这样可以确保将每一列的值正确插入数据库中。 创建一个新的IDataSet对象并返回创建的对象。 ColumnSensingFlatXMLDataSetLoader类的源代码如下所示 import com.github.springtestdbunit.dataset.AbstractDataSetLoader;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.springframework.core.io.Resource;import java.io.InputStream;public class ColumnSensingFlatXMLDataSetLoader extends AbstractDataSetLoader {Overrideprotected IDataSet createDataSet(Resource resource) throws Exception {FlatXmlDataSetBuilder builder new FlatXmlDataSetBuilder();builder.setColumnSensing(true);InputStream inputStream resource.getInputStream();try {return builder.build(inputStream);} finally {inputStream.close();}}
} 但是创建自定义数据集加载器类还不够。 加载数据集时我们仍然必须配置测试以使用此类。 我们可以通过使用DbUnitConfiguration批注注释测试类并将其dataSetLoader属性的值设置为ColumnSensingFlatXMLDataSetLoader.class来实现 。 让我们继续看这是如何完成的。 配置我们的集成测试 我们可以按照以下步骤配置集成测试 确保测试由Spring SpringJUnit4ClassRunner执行。 我们可以通过使用RunWith注释对测试类进行注释并将其值设置为SpringJUnit4ClassRunner.class来实现 。 通过使用ContextConfiguration批注注释测试类来加载应用程序上下文并配置使用的应用程序上下文配置类或文件。 用WebAppConfiguration批注注释测试类。 这可以确保为集成测试加载的应用程序上下文是WebApplicationContext 。 用TestExecutionListeners注释为类添加注释并传递标准的Spring侦听器和DBUnitTestExecutionListener作为其值。 DBUnitTestExecutionListener确保Spring处理从测试类中找到的DbUnit批注。 通过使用DbUnitConfiguration批注注释测试类将测试类配置为使用我们的自定义数据集加载器。 将其dataSetLoader属性的值设置为ColumnSensingFlatXMLDataSetLoader.class 。 将FilterChainProxy字段添加到测试类并使用Autowired批注对该字段进行批注。 将WebApplicationContext字段添加到测试类并使用Autowired批注对该字段进行批注。 将MockMvc字段添加到测试类。 在测试类中添加一个setUp方法并使用Before注释对该方法进行注释以确保在每个测试方法之前都调用此方法。 通过使用MockMvcBuilders类实现setUp方法并创建一个新的MockMvc对象。 空测试类的源代码如下所示 import com.github.springtestdbunit.DbUnitTestExecutionListener;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(classes {ExampleApplicationContext.class, IntegrationTestContext.class})
//ContextConfiguration(locations {classpath:exampleApplicationContext.xml})
WebAppConfiguration
TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class })
DbUnitConfiguration(dataSetLoader ColumnSensingFlatXMLDataSetLoader.class)
public class ITTest {Autowiredprivate FilterChainProxy springSecurityFilterChain;Autowiredprivate WebApplicationContext webApplicationContext;private MockMvc mockMvc;Beforepublic void setUp() {mockMvc MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilter(springSecurityFilterChain).build();}
} 如果您需要有关我们的集成测试的配置的更多信息建议您阅读以下博客文章 Spring MVC控制器的单元测试配置介绍了如何配置Spring MVC Test框架。 本教程讨论了单元测试但仍应进一步阐明该问题。 Spring Data JPA教程集成测试描述了如何为Spring Data JPA存储库编写集成测试。 如果您想看一下Spring Test DBUnit的配置这篇博客文章可能有助于您了解它。 Spring MVC应用程序的集成测试安全性描述了如何编写Spring MVC应用程序的安全性测试。 本教程基于Spring Security 3.1但是它仍然可以帮助您了解如何编写这些测试。 现在我们已经了解了如何配置集成测试。 让我们继续并创建一些在集成测试中使用的测试实用程序类。 创建测试实用程序类 接下来我们将创建在集成测试中使用的三个实用程序类 我们将创建IntegrationTestConstants类其中包含多个集成测试中使用的常量。 我们将创建用于为集成测试创建ProviderSignInAttempt对象的类。 我们将创建一个测试数据构建器类该类用于创建CsrfToken对象。 让我们找出为什么我们必须创建这些类以及如何创建它们。 创建IntegrationTestConstants类 当我们编写集成或单元测试时有时我们需要在许多测试类中使用相同的信息。 将这些信息复制到所有测试类是一个坏主意因为这会使我们的测试难以维护和理解。 相反我们应该将此信息放在一个类中并在需要时从该类中获取。 IntegrationTestConstants类包含以下信息这些信息可在多个测试类中使用 它具有与Spring Security 3.2的CSRF保护相关的常数。 这些常量包括包含CSRF令牌的HTTP标头的名称包含CSRF令牌的值的请求参数的名称包含CsrfToken对象的会话属性的名称以及CSRF令牌的值。 它包含User枚举该枚举指定了我们的集成测试中使用的用户。 每个用户都有一个用户名和密码这不是必需的。 该枚举的信息用于两个目的 用于指定登录用户。 当我们对受保护的功能需要某种授权的功能进行集成测试时这很有用。 在为登录功能编写集成测试时我们需要指定尝试登录该应用程序的用户的用户名和密码。 IntegrationTestConstants类的源代码如下所示 import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;public class IntegrationTestConstants {public static final String CSRF_TOKEN_HEADER_NAME X-CSRF-TOKEN;public static final String CSRF_TOKEN_REQUEST_PARAM_NAME _csrf;public static final String CSRF_TOKEN_SESSION_ATTRIBUTE_NAME HttpSessionCsrfTokenRepository.class.getName().concat(.CSRF_TOKEN);public static final String CSRF_TOKEN_VALUE f416e226-bebc-401d-a1ed-f10f47aa9c56;public enum User {FACEBOOK_USER(facebooksocialuser.com, null),REGISTERED_USER(registereduser.com, password),TWITTER_USER(twittersocialuser.com, null);private String password;private String username;private User(String username, String password) {this.password password;this.username username;}public String getPassword() {return password;}public String getUsername() {return username;}}
}创建ProviderSignInAttempt对象 在为示例应用程序编写单元测试时我们快速浏览了ProviderSignInUtils类并意识到我们必须找到一种创建ProviderSignInAttempt对象的方法。 我们通过创建在单元测试中使用的存根类来解决该问题。 这个存根类使我们可以配置返回的Connection 对象并验证特定的连接是否“持久化到数据库”。 但是我们的存根类未持久连接到使用的数据库。 而是将用户的用户ID存储到Set对象。 因为现在我们想将连接数据持久化到数据库所以我们必须对存根类进行更改。 我们可以通过对TestProviderSignInAttempt对象进行以下更改来进行这些更改 将一个专用的usersConnectionRepositorySet字段添加到TestProviderSignInAttempt类。 该字段的类型为布尔值 其默认值为false。 该字段描述了我们是否可以持久连接到使用的数据存储。 将一个新的构造函数参数添加到TestProviderSignInAttempt类的构造函数中。 此参数的类型为UsersConnectionRepository 用于持久连接到使用的数据存储。 通过执行以下步骤来实现构造函数 调用超类的构造函数并将Connection 和UsersConnectionRepository对象作为构造函数参数传递。 存储对Connection 对象的引用该引用作为连接字段的构造函数参数给出。 如果作为构造函数参数给出的UsersConnectionRepository对象不为null则将usersConnectionRepositoryField的值设置为true。 通过执行以下步骤来实现addConnection方法 将作为方法参数给出的用户ID添加到连接 Set中 。 如果在创建新的TestProviderSignInAttempt对象时设置了UsersConnectionRepository对象请调用ProviderSignInAttempt类的addConnection方法并将用户ID作为方法参数传递。 TestProviderSignInAttempt类的源代码如下所示修改的部分突出显示 import org.springframework.social.connect.Connection;
import org.springframework.social.connect.UsersConnectionRepository;import java.util.HashSet;
import java.util.Set;public class TestProviderSignInAttempt extends ProviderSignInAttempt {private Connection? connection;private SetString connections new HashSet();private boolean usersConnectionRepositorySet false;public TestProviderSignInAttempt(Connection? connection, UsersConnectionRepository usersConnectionRepository) {super(connection, null, usersConnectionRepository);this.connection connection;if (usersConnectionRepository ! null) {this.usersConnectionRepositorySet true;}}Overridepublic Connection? getConnection() {return connection;}Overridevoid addConnection(String userId) {connections.add(userId);if (usersConnectionRepositorySet) {super.addConnection(userId);}}public SetString getConnections() {return connections;}
} 因为我们使用TestProviderSignInAttemptBuilder来构建新的TestProviderSignInAttempt对象所以我们也必须对该类进行更改。 我们可以按照以下步骤进行这些更改 将私有的usersConnectionRepository字段添加到TestProviderSignInAttemptBuilder类并将其类型设置为UsersConnectionRepository 。 将usersConnectionRepository方法添加到该类。 将对 UsersConnectionRepository对象的引用设置为usersConnectionRepository字段并返回对构建器对象的引用。 修改build方法的最后一行并使用我们之前创建的新构造函数创建一个新的TestProviderSignInAttempt对象。 TestProviderSignInAttemptBuilder类的源代码如下所示修改的部分突出显示 import org.springframework.social.connect.*;
import org.springframework.social.connect.web.TestProviderSignInAttempt;public class TestProviderSignInAttemptBuilder {private String accessToken;private String displayName;private String email;private Long expireTime;private String firstName;private String imageUrl;private String lastName;private String profileUrl;private String providerId;private String providerUserId;private String refreshToken;private String secret;private UsersConnectionRepository usersConnectionRepository;public TestProviderSignInAttemptBuilder() {}public TestProviderSignInAttemptBuilder accessToken(String accessToken) {this.accessToken accessToken;return this;}public TestProviderSignInAttemptBuilder connectionData() {return this;}public TestProviderSignInAttemptBuilder displayName(String displayName) {this.displayName displayName;return this;}public TestProviderSignInAttemptBuilder email(String email) {this.email email;return this;}public TestProviderSignInAttemptBuilder expireTime(Long expireTime) {this.expireTime expireTime;return this;}public TestProviderSignInAttemptBuilder firstName(String firstName) {this.firstName firstName;return this;}public TestProviderSignInAttemptBuilder imageUrl(String imageUrl) {this.imageUrl imageUrl;return this;}public TestProviderSignInAttemptBuilder lastName(String lastName) {this.lastName lastName;return this;}public TestProviderSignInAttemptBuilder profileUrl(String profileUrl) {this.profileUrl profileUrl;return this;}public TestProviderSignInAttemptBuilder providerId(String providerId) {this.providerId providerId;return this;}public TestProviderSignInAttemptBuilder providerUserId(String providerUserId) {this.providerUserId providerUserId;return this;}public TestProviderSignInAttemptBuilder refreshToken(String refreshToken) {this.refreshToken refreshToken;return this;}public TestProviderSignInAttemptBuilder secret(String secret) {this.secret secret;return this;}public TestProviderSignInAttemptBuilder usersConnectionRepository(UsersConnectionRepository usersConnectionRepository) {this.usersConnectionRepository usersConnectionRepository;return this;}public TestProviderSignInAttemptBuilder userProfile() {return this;}public TestProviderSignInAttempt build() {ConnectionData connectionData new ConnectionData(providerId,providerUserId,displayName,profileUrl,imageUrl,accessToken,secret,refreshToken,expireTime);UserProfile userProfile new UserProfileBuilder().setEmail(email).setFirstName(firstName).setLastName(lastName).build();Connection connection new TestConnection(connectionData, userProfile);return new TestProviderSignInAttempt(connection, usersConnectionRepository);}
}创建CsrfToken对象 因为我们的示例应用程序使用了Spring Security 3.2提供的CSRF保护所以我们必须找出一种在集成测试中创建有效CSRF令牌的方法。 CsrfToken接口声明了提供有关预期CSRF令牌信息的方法。 该接口具有一个称为DefaultCsrfToken的实现。 换句话说我们必须找出一种创建新的DefaultCsrfToken对象的方法。 DefaultCsrfToken类只有一个构造函数 我们可以在集成测试中创建新的DefaultCsrfToken对象时使用它。 问题是这不是很可读。 相反我们将创建一个测试数据构建器类 该类提供了用于创建新CsrfToken对象的流利API 。 我们可以按照以下步骤创建此类 创建一个CsrfTokenBuilder类。 在创建的类中添加一个私有的headerName字段。 将私有requestParameterName字段添加到创建的类。 将私有tokenValue字段添加到创建的类。 向创建的类添加发布构造函数。 添加用于设置headerName requestParameterName和tokenValue字段的字段值的方法。 向所创建的类中添加一个build方法并将其返回类型设置为CsrfToken 。 通过执行以下步骤来实现此方法 创建一个新的DefaultCsrfToken对象并提供CSRF令牌头的名称CSRF令牌请求参数的名称以及CSRF令牌的值作为构造函数参数。 返回创建的对象。 CsrfTokenBuilder类的源代码如下所示 import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;public class CsrfTokenBuilder {private String headerName;private String requestParameterName;private String tokenValue;public CsrfTokenBuilder() {}public CsrfTokenBuilder headerName(String headerName) {this.headerName headerName;return this;}public CsrfTokenBuilder requestParameterName(String requestParameterName) {this.requestParameterName requestParameterName;return this;}public CsrfTokenBuilder tokenValue(String tokenValue) {this.tokenValue tokenValue;return this;}public CsrfToken build() {return new DefaultCsrfToken(headerName, requestParameterName, tokenValue);}
} 我们可以使用以下代码创建新的CsrfToken对象 CsrfToken csrfToken new CsrfTokenBuilder().headerName(IntegrationTestConstants.CSRF_TOKEN_HEADER_NAME).requestParameterName(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME).tokenValue(IntegrationTestConstants.CSRF_TOKEN_VALUE).build(); 现在我们已经创建了必需的测试实用程序类。 让我们继续并开始为示例应用程序编写集成测试。 编写集成测试 我们终于准备好为示例应用程序编写一些集成测试。 我们将编写以下集成测试 我们将编写集成测试以确保表单登录正常工作。 我们将编写集成测试以验证使用社交登录时注册是否正常运行。 但是在开始编写这些集成测试之前我们将学习如何为Spring Security提供有效的CSRF令牌。 向Spring Security提供有效的CSRF令牌 之前我们了解了如何在集成测试中创建CsrfToken对象。 但是我们仍然必须找出一种将这些CSRF令牌提供给Spring Security的方法。 现在是时候仔细研究一下Spring Security处理CSRF令牌的方式了。 CsrfTokenRepository接口声明生成保存和加载CSRF令牌所需的方法。 该接口的默认实现是HttpSessionCsrfTokenRepository类该类将CSRF令牌存储到HTTP会话。 我们需要找到以下问题的答案 CSRF令牌如何保存到HTTP会话 如何从HTTP会话加载CSRF令牌 通过查看HttpSessionCsrfTokenRepository类的源代码我们可以找到这些问题的答案。 HttpSessionCsrfTokenRepository类的相关部分如下所示 import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository {private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME HttpSessionCsrfTokenRepository.class.getName().concat(.CSRF_TOKEN);private String sessionAttributeName DEFAULT_CSRF_TOKEN_ATTR_NAME;public void saveToken(CsrfToken token, HttpServletRequest request,HttpServletResponse response) {if (token null) {HttpSession session request.getSession(false);if (session ! null) {session.removeAttribute(sessionAttributeName);}} else {HttpSession session request.getSession();session.setAttribute(sessionAttributeName, token);}}public CsrfToken loadToken(HttpServletRequest request) {HttpSession session request.getSession(false);if (session null) {return null;}return (CsrfToken) session.getAttribute(sessionAttributeName);}//Other methods are omitted.
} 现在很清楚CSRF令牌作为CsrfToken对象存储到HTTP会话并且通过使用sessionAttributeName属性的值重试和存储这些对象。 这意味着如果我们想向Spring Security提供有效的CSRF令牌则必须遵循以下步骤 通过使用我们的测试数据生成器创建一个新的CsrfToken对象。 发送CSRF令牌的值作为请求参数。 将创建的DefaultCsrfToken对象存储到HTTP会话中以便HttpSessionCsrfTokenRepository找到它。 我们的虚拟测试的源代码如下所示 import org.junit.Test;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.test.web.servlet.MockMvc;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;public class ITCSRFTest {private MockMvc mockMvc;Testpublic void test() throws Exception {//1. Create a new CSRF tokenCsrfToken csrfToken new CsrfTokenBuilder().headerName(IntegrationTestConstants.CSRF_TOKEN_HEADER_NAME).requestParameterName(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME).tokenValue(IntegrationTestConstants.CSRF_TOKEN_VALUE).build();mockMvc.perform(post(/login/authenticate)//2. Send the value of the CSRF token as request parameter.param(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME, IntegrationTestConstants.CSRF_TOKEN_VALUE)//3. Set the created CsrfToken object to session so that the CsrfTokenRepository finds it.sessionAttr(IntegrationTestConstants.CSRF_TOKEN_SESSION_ATTRIBUTE_NAME, csrfToken))//Add assertions here.}
} 理论足够了。 现在我们准备为我们的应用程序编写一些集成测试。 首先将集成编写到示例应用程序的登录功能中。 编写登录功能测试 现在该为示例应用程序的登录功能编写集成测试了。 我们将为此编写以下集成测试 我们将编写一个集成测试以确保登录成功后一切正常。 我们将编写一个集成测试以确保登录失败时一切正常。 这两个集成测试都使用相同的DbUnit数据集文件 users.xml 将数据库初始化为已知状态其内容如下所示 ?xml version1.0 encodingUTF-8?
datasetuser_accounts id1creation_time2014-02-20 11:13:28emailfacebooksocialuser.comfirst_nameFacebooklast_nameUsermodification_time2014-02-20 11:13:28roleROLE_USERsign_in_providerFACEBOOKversion0/user_accounts id2creation_time2014-02-20 11:13:28emailtwittersocialuser.comfirst_nameTwitterlast_nameUsermodification_time2014-02-20 11:13:28roleROLE_USERsign_in_providerTWITTERversion0/user_accounts id3creation_time2014-02-20 11:13:28emailregistereduser.comfirst_nameRegisteredUserlast_nameUsermodification_time2014-02-20 11:13:28password$2a$10$PFSfOaC2IFPG.1HjO05KoODRVSdESQ5q7ek4IyzVfTf.VWlKDa/.eroleROLE_USERversion0/UserConnection/
/dataset 让我们开始吧。 测试1登录成功 我们可以按照以下步骤编写第一个集成测试 使用DatabaseSetup批注对测试类进行批注并配置用于在调用集成测试之前将数据库初始化为已知状态的数据集。 创建一个新的CsrfToken对象。 请按照以下步骤将POST请求发送到url/ login / authenticate 设置用户名和密码请求参数的值。 使用正确的密码。 将CSRF令牌的值设置为请求。 将创建的CsrfToken设置为session。 确保返回HTTP状态代码302。 验证请求是否重定向到URL“ /”。 我们的集成测试的源代码如下所示 import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(classes {ExampleApplicationContext.class, IntegrationTestContext.class})
//ContextConfiguration(locations {classpath:exampleApplicationContext.xml})
WebAppConfiguration
TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class })
DbUnitConfiguration(dataSetLoader ColumnSensingFlatXMLDataSetLoader.class)
DatabaseSetup(/net/petrikainulainen/spring/social/signinmvc/user/users.xml)
public class ITFormLoginTest {private static final String REQUEST_PARAM_PASSWORD password;private static final String REQUEST_PARAM_USERNAME username;//Some fields are omitted for the sake of clarityprivate MockMvc mockMvc;//The setUp() method is omitted for the sake of clarify.Testpublic void login_CredentialsAreCorrect_ShouldRedirectUserToFrontPage() throws Exception {CsrfToken csrfToken new CsrfTokenBuilder().headerName(IntegrationTestConstants.CSRF_TOKEN_HEADER_NAME).requestParameterName(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME).tokenValue(IntegrationTestConstants.CSRF_TOKEN_VALUE).build();mockMvc.perform(post(/login/authenticate).param(REQUEST_PARAM_USERNAME, IntegrationTestConstants.User.REGISTERED_USER.getUsername()).param(REQUEST_PARAM_PASSWORD, IntegrationTestConstants.User.REGISTERED_USER.getPassword()).param(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME, IntegrationTestConstants.CSRF_TOKEN_VALUE).sessionAttr(IntegrationTestConstants.CSRF_TOKEN_SESSION_ATTRIBUTE_NAME, csrfToken)).andExpect(status().isMovedTemporarily()).andExpect(redirectedUrl(/));}
}测试2登录失败 我们可以按照以下步骤编写第二个集成测试 使用DatabaseSetup批注对测试类进行批注并配置用于在调用集成测试之前将数据库初始化为已知状态的数据集。 创建一个新的CsrfToken对象。 请按照以下步骤将POST请求发送到url/ login / authenticate 设置用户名和密码请求参数的值。 使用错误的密码。 将CSRF令牌的值设置为请求作为请求参数。 将创建的CsrfToken对象设置为session。 确保返回HTTP状态代码302。 验证请求是否重定向到URL/ loginerror bad_credentials。 我们的集成测试的源代码如下所示 import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(classes {ExampleApplicationContext.class, IntegrationTestContext.class})
//ContextConfiguration(locations {classpath:exampleApplicationContext.xml})
WebAppConfiguration
TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class })
DbUnitConfiguration(dataSetLoader ColumnSensingFlatXMLDataSetLoader.class)
DatabaseSetup(/net/petrikainulainen/spring/social/signinmvc/user/users.xml)
public class ITFormLoginTest {private static final String REQUEST_PARAM_PASSWORD password;private static final String REQUEST_PARAM_USERNAME username;//Some fields are omitted for the sake of clarityprivate MockMvc mockMvc;//The setUp() method is omitted for the sake of clarify.Testpublic void login_InvalidPassword_ShouldRedirectUserToLoginForm() throws Exception {CsrfToken csrfToken new CsrfTokenBuilder().headerName(IntegrationTestConstants.CSRF_TOKEN_HEADER_NAME).requestParameterName(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME).tokenValue(IntegrationTestConstants.CSRF_TOKEN_VALUE).build();mockMvc.perform(post(/login/authenticate).param(REQUEST_PARAM_USERNAME, IntegrationTestConstants.User.REGISTERED_USER.getUsername()).param(REQUEST_PARAM_PASSWORD, invalidPassword).param(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME, IntegrationTestConstants.CSRF_TOKEN_VALUE).sessionAttr(IntegrationTestConstants.CSRF_TOKEN_SESSION_ATTRIBUTE_NAME, csrfToken)).andExpect(status().isMovedTemporarily()).andExpect(redirectedUrl(/login?errorbad_credentials));}
}为注册功能编写测试 我们将为注册功能编写以下集成测试 我们将编写一个集成测试以确保当用户使用社交登录创建新用户帐户时我们的应用程序正常运行但提交的注册表格的验证失败。 我们将编写一个集成测试该测试通过使用社交登录名和从数据库中找到的电子邮件地址来验证用户创建新用户帐户时一切是否正常运行。 我们将编写一个集成测试以确保可以通过使用社交登录来创建新的用户帐户。 让我们开始吧。 测试1验证失败 我们可以按照以下步骤编写第一个集成测试 将UsersConnectionRepository字段添加到测试类并使用Autowired批注对其进行批注。 使用DatabaseSetup批注注释测试方法并配置数据集该数据集用于在运行集成测试之前将数据库初始化为已知状态。 创建一个新的TestProviderSignInAttempt对象。 记住要设置使用的UsersConnectionRepository对象。 创建一个新的RegistrationForm对象并设置其signInProvider字段的值。 创建一个新的CsrfToken对象。 请按照以下步骤将POST请求发送到url/ user / register 将请求的内容类型设置为“ application / x-www-form-urlencoded”。 将表单对象转换为url编码的字节并将其设置为请求的正文。 将创建的TestProviderSignInAttempt对象设置为session。 将CSRF令牌的值设置为请求作为请求参数。 将创建的CsrfToken对象设置为session。 将创建的表单对象设置为session。 确保返回HTTP请求状态200。 确保渲染视图的名称为“ user / registrationForm”。 验证请求是否转发到URL/WEB-INF/jsp/user/registrationForm.jsp。 验证名为“ user”的模型属性的字段正确。 确保名为user的模型属性在email firstName和lastName字段中存在字段错误。 使用ExpectedDatabase批注注释测试方法并确保未将新用户帐户保存到数据库使用用于初始化数据库的相同数据集。 我们的集成测试的源代码如下所示 import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import com.github.springtestdbunit.assertion.DatabaseAssertionMode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.support.TestProviderSignInAttemptBuilder;
import org.springframework.social.connect.web.ProviderSignInAttempt;
import org.springframework.social.connect.web.TestProviderSignInAttempt;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;import static net.petrikainulainen.spring.social.signinmvc.user.controller.TestProviderSignInAttemptAssert.assertThatSignIn;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(classes {ExampleApplicationContext.class, IntegrationTestContext.class})
//ContextConfiguration(locations {classpath:exampleApplicationContext.xml})
WebAppConfiguration
TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class })
DbUnitConfiguration(dataSetLoader ColumnSensingFlatXMLDataSetLoader.class)
public class ITRegistrationControllerTest {Autowiredprivate UsersConnectionRepository usersConnectionRepository;//Some fields are omitted for the sake of clarity.private MockMvc mockMvc;//The setUp() is omitted for the sake of clarity.TestDatabaseSetup(no-users.xml)ExpectedDatabase(valueno-users.xml, assertionMode DatabaseAssertionMode.NON_STRICT)public void registerUserAccount_SocialSignInAndEmptyForm_ShouldRenderRegistrationFormWithValidationErrors() throws Exception {TestProviderSignInAttempt socialSignIn new TestProviderSignInAttemptBuilder().connectionData().accessToken(accessToken).displayName(John Smith).expireTime(100000L).imageUrl(https://www.twitter.com/images/johnsmith.jpg).profileUrl(https://www.twitter.com/johnsmith).providerId(twitter).providerUserId(johnsmith).refreshToken(refreshToken).secret(secret).usersConnectionRepository(usersConnectionRepository).userProfile().email(john.smithgmail.com).firstName(John).lastName(Smith).build();RegistrationForm userAccountData new RegistrationFormBuilder().signInProvider(SocialMediaService.TWITTER).build();CsrfToken csrfToken new CsrfTokenBuilder().headerName(IntegrationTestConstants.CSRF_TOKEN_HEADER_NAME).requestParameterName(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME).tokenValue(IntegrationTestConstants.CSRF_TOKEN_VALUE).build();mockMvc.perform(post(/user/register).contentType(MediaType.APPLICATION_FORM_URLENCODED).content(TestUtil.convertObjectToFormUrlEncodedBytes(userAccountData)).sessionAttr(ProviderSignInAttempt.SESSION_ATTRIBUTE, socialSignIn).param(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME, IntegrationTestConstants.CSRF_TOKEN_VALUE).sessionAttr(IntegrationTestConstants.CSRF_TOKEN_SESSION_ATTRIBUTE_NAME, csrfToken).sessionAttr(user, userAccountData)).andExpect(status().isOk()).andExpect(view().name(user/registrationForm)).andExpect(forwardedUrl(/WEB-INF/jsp/user/registrationForm.jsp)).andExpect(model().attribute(user, allOf(hasProperty(email, isEmptyOrNullString()),hasProperty(firstName, isEmptyOrNullString()),hasProperty(lastName, isEmptyOrNullString()),hasProperty(password, isEmptyOrNullString()),hasProperty(passwordVerification, isEmptyOrNullString()),hasProperty(signInProvider, is(SocialMediaService.TWITTER))))).andExpect(model().attributeHasFieldErrors(user, email, firstName, lastName));}
} 我们的集成测试使用一个名为no-users.xml的DbUnit数据集文件该文件如下所示 ?xml version1.0 encodingUTF-8?
datasetuser_accounts/UserConnection/
/dataset 测试2从数据库中找到电子邮件地址 我们可以按照以下步骤编写第二个集成测试 将UsersConnectionRepository字段添加到测试类并使用Autowired批注对其进行批注。 使用DatabaseSetup批注注释测试方法并配置数据集该数据集用于在运行集成测试之前将数据库初始化为已知状态。 创建一个新的TestProviderSignInAttempt对象。 记住要设置使用的UsersConnectionRepository对象。 创建一个新的RegistrationForm对象并设置其email firstName lastName和signInProvider字段的值。 使用现有的电子邮件地址。 创建一个新的CsrfToken对象。 请按照以下步骤将POST请求发送到url/ user / register 将请求的内容类型设置为“ application / x-www-form-urlencoded”。 将表单对象转换为url编码的字节并将其设置为请求的正文。 将创建的TestProviderSignInAttempt对象设置为session。 将CSRF令牌的值设置为请求作为请求参数。 将创建的CsrfToken对象设置为session。 将创建的表单对象设置为session。 确保返回HTTP请求状态200。 确保渲染视图的名称为“ user / registrationForm”。 验证请求是否转发到URL/WEB-INF/jsp/user/registrationForm.jsp。 验证名为“ user”的模型属性的字段正确。 确保名为“用户”的模型属性在电子邮件字段中存在字段错误。 使用ExpectedDatabase批注注释测试方法并确保未将新用户帐户保存到数据库使用用于初始化数据库的相同数据集。 我们的集成测试的源代码如下所示 import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import com.github.springtestdbunit.assertion.DatabaseAssertionMode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.support.TestProviderSignInAttemptBuilder;
import org.springframework.social.connect.web.ProviderSignInAttempt;
import org.springframework.social.connect.web.TestProviderSignInAttempt;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(classes {ExampleApplicationContext.class, IntegrationTestContext.class})
//ContextConfiguration(locations {classpath:exampleApplicationContext.xml})
WebAppConfiguration
TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class })
DbUnitConfiguration(dataSetLoader ColumnSensingFlatXMLDataSetLoader.class)
public class ITRegistrationControllerTest {Autowiredprivate UsersConnectionRepository usersConnectionRepository;//Some fields are omitted for the sake of clarity.private MockMvc mockMvc;//The setUp() is omitted for the sake of clarity.TestDatabaseSetup(/net/petrikainulainen/spring/social/signinmvc/user/users.xml)ExpectedDatabase(value /net/petrikainulainen/spring/social/signinmvc/user/users.xml, assertionMode DatabaseAssertionMode.NON_STRICT)public void registerUserAccount_SocialSignInAndEmailExist_ShouldRenderRegistrationFormWithFieldError() throws Exception {TestProviderSignInAttempt socialSignIn new TestProviderSignInAttemptBuilder().connectionData().accessToken(accessToken).displayName(John Smith).expireTime(100000L).imageUrl(https://www.twitter.com/images/johnsmith.jpg).profileUrl(https://www.twitter.com/johnsmith).providerId(twitter).providerUserId(johnsmith).refreshToken(refreshToken).secret(secret).usersConnectionRepository(usersConnectionRepository).userProfile().email(IntegrationTestConstants.User.REGISTERED_USER.getUsername()).firstName(John).lastName(Smith).build();RegistrationForm userAccountData new RegistrationFormBuilder().email(IntegrationTestConstants.User.REGISTERED_USER.getUsername()).firstName(John).lastName(Smith).signInProvider(SocialMediaService.TWITTER).build();CsrfToken csrfToken new CsrfTokenBuilder().headerName(IntegrationTestConstants.CSRF_TOKEN_HEADER_NAME).requestParameterName(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME).tokenValue(IntegrationTestConstants.CSRF_TOKEN_VALUE).build();mockMvc.perform(post(/user/register).contentType(MediaType.APPLICATION_FORM_URLENCODED).content(TestUtil.convertObjectToFormUrlEncodedBytes(userAccountData)).sessionAttr(ProviderSignInAttempt.SESSION_ATTRIBUTE, socialSignIn).param(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME, IntegrationTestConstants.CSRF_TOKEN_VALUE).sessionAttr(IntegrationTestConstants.CSRF_TOKEN_SESSION_ATTRIBUTE_NAME, csrfToken).sessionAttr(user, userAccountData)).andExpect(status().isOk()).andExpect(view().name(user/registrationForm)).andExpect(forwardedUrl(/WEB-INF/jsp/user/registrationForm.jsp)).andExpect(model().attribute(user, allOf(hasProperty(email, is(IntegrationTestConstants.User.REGISTERED_USER.getUsername())),hasProperty(firstName, is(John)),hasProperty(lastName, is(Smith)),hasProperty(password, isEmptyOrNullString()),hasProperty(passwordVerification, isEmptyOrNullString()),hasProperty(signInProvider, is(SocialMediaService.TWITTER))))).andExpect(model().attributeHasFieldErrors(user, email));}
} 该集成测试使用一个名为users.xml的DbUnit数据集该数据集如下所示 ?xml version1.0 encodingUTF-8?
datasetuser_accounts id1 creation_time2014-02-20 11:13:28 emailfacebooksocialuser.com first_nameFacebook last_nameUser modification_time2014-02-20 11:13:28 roleROLE_USER sign_in_providerFACEBOOK version0/user_accounts id2 creation_time2014-02-20 11:13:28 emailtwittersocialuser.com first_nameTwitter last_nameUser modification_time2014-02-20 11:13:28 roleROLE_USER sign_in_providerTWITTER version0/user_accounts id3 creation_time2014-02-20 11:13:28 emailregistereduser.com first_nameRegisteredUser last_nameUser modification_time2014-02-20 11:13:28 password$2a$10$PFSfOaC2IFPG.1HjO05KoODRVSdESQ5q7ek4IyzVfTf.VWlKDa/.e roleROLE_USER version0/UserConnection/
/dataset 测试3注册成功 我们可以按照以下步骤编写第三项集成测试 将UsersConnectionRepository字段添加到测试类并使用Autowired批注对其进行批注。 使用DatabaseSetup批注注释测试方法并配置数据集该数据集用于在运行集成测试之前将数据库初始化为已知状态。 创建一个新的TestProviderSignInAttempt对象。 记住要设置使用的UsersConnectionRepository对象。 创建一个新的RegistrationForm对象并设置其email firstName lastName和signInProvider字段的值。 创建一个新的CsrfToken对象。 请按照以下步骤将POST请求发送到url/ user / register 将请求的内容类型设置为“ application / x-www-form-urlencoded”。 将表单对象转换为url编码的字节并将其设置为请求的正文。 将创建的TestProviderSignInAttempt对象设置为session。 将CSRF令牌的值设置为请求作为请求参数。 将创建的CsrfToken对象设置为session。 将创建的表单对象设置为session。 确保返回HTTP请求状态302。 验证请求是否重定向到URL/。 这还可以确保创建的用户已登录因为匿名用户无法访问该URL。 使用ExpectedDatabase批注对测试方法进行批注并确保将新用户帐户保存到数据库中并且与使用的社交媒体提供商的连接得以持久。 我们的集成测试的源代码如下所示 import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import com.github.springtestdbunit.assertion.DatabaseAssertionMode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.support.TestProviderSignInAttemptBuilder;
import org.springframework.social.connect.web.ProviderSignInAttempt;
import org.springframework.social.connect.web.TestProviderSignInAttempt;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(classes {ExampleApplicationContext.class, IntegrationTestContext.class})
//ContextConfiguration(locations {classpath:exampleApplicationContext.xml})
WebAppConfiguration
TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class })
DbUnitConfiguration(dataSetLoader ColumnSensingFlatXMLDataSetLoader.class)
public class ITRegistrationControllerTest2 {Autowiredprivate UsersConnectionRepository usersConnectionRepository;//Some fields are omitted for the sake of clarity.private MockMvc mockMvc;//The setUp() is omitted for the sake of clarity.TestDatabaseSetup(no-users.xml)ExpectedDatabase(valueregister-social-user-expected.xml, assertionMode DatabaseAssertionMode.NON_STRICT)public void registerUserAccount_SocialSignIn_ShouldCreateNewUserAccountAndRenderHomePage() throws Exception {TestProviderSignInAttempt socialSignIn new TestProviderSignInAttemptBuilder().connectionData().accessToken(accessToken).displayName(John Smith).expireTime(100000L).imageUrl(https://www.twitter.com/images/johnsmith.jpg).profileUrl(https://www.twitter.com/johnsmith).providerId(twitter).providerUserId(johnsmith).refreshToken(refreshToken).secret(secret).usersConnectionRepository(usersConnectionRepository).userProfile().email(john.smithgmail.com).firstName(John).lastName(Smith).build();RegistrationForm userAccountData new RegistrationFormBuilder().email(john.smithgmail.com).firstName(John).lastName(Smith).signInProvider(SocialMediaService.TWITTER).build();CsrfToken csrfToken new CsrfTokenBuilder().headerName(IntegrationTestConstants.CSRF_TOKEN_HEADER_NAME).requestParameterName(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME).tokenValue(IntegrationTestConstants.CSRF_TOKEN_VALUE).build();mockMvc.perform(post(/user/register).contentType(MediaType.APPLICATION_FORM_URLENCODED).content(TestUtil.convertObjectToFormUrlEncodedBytes(userAccountData)).sessionAttr(ProviderSignInAttempt.SESSION_ATTRIBUTE, socialSignIn).param(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME, IntegrationTestConstants.CSRF_TOKEN_VALUE).sessionAttr(IntegrationTestConstants.CSRF_TOKEN_SESSION_ATTRIBUTE_NAME, csrfToken).sessionAttr(user, userAccountData)).andExpect(status().isMovedTemporarily()).andExpect(redirectedUrl(/));}
} 用于将数据库初始化为已知状态的数据集 no-users.xml 如下所示 ?xml version1.0 encodingUTF-8?
datasetuser_accounts/UserConnection/
/dataset 名为register-social-user-expected.xml的DbUnit数据集用于验证是否成功创建了用户帐户并且与使用的社交登录提供者的连接已持久保存到数据库中。 它看起来如下 ?xml version1.0 encodingUTF-8?
datasetuser_accounts emailjohn.smithgmail.com first_nameJohn last_nameSmith roleROLE_USER sign_in_providerTWITTERversion0/UserConnection userIdjohn.smithgmail.comproviderIdtwitterproviderUserIdjohnsmithrank1displayNameJohn SmithprofileUrlhttps://www.twitter.com/johnsmithimageUrlhttps://www.twitter.com/images/johnsmith.jpgaccessTokenaccessTokensecretsecretrefreshTokenrefreshTokenexpireTime100000/
/dataset摘要 现在我们已经了解了如何为使用Spring Social 1.1.0的常规Spring MVC应用程序编写集成测试。 本教程教会了我们很多东西但是这两件事是本博客文章的主要课程 我们了解了如何通过创建ProviderSignInAttempt对象并在集成测试中使用它们来“模拟”社交登录。 我们学习了如何创建CSRF令牌并将创建的令牌提供给Spring Security。 让我们花点时间来分析此博客文章中描述的方法的优缺点 优点 我们可以编写集成测试而无需使用外部社交登录提供程序。 这使我们的测试不那么脆弱更易于维护。 Spring Social ProviderSignInAttempt 和Spring Security CSRF保护 CsrfToken 的实现细节被“隐藏”以测试数据构建器类。 这使我们的测试更具可读性更易于维护。 缺点 本教程没有描述我们如何编写社交登录集成测试使用社交登录提供程序登录。 我试图找出一种无需使用外部登录提供程序即可编写这些测试的方法但我只是用光了时间这似乎很复杂我想发布此博客文章。 这篇博客文章结束了我的“向Spring MVC应用程序添加社交登录”教程。 我将写一个类似的教程描述如何在将来将社交登录添加到基于Spring的REST API中。 同时您可能需要阅读本教程的其他部分 。 您可以从Github获得此博客文章的示例应用程序。 翻译自: https://www.javacodegeeks.com/2014/03/adding-social-sign-in-to-a-spring-mvc-web-application-integration-testing.html集成spring mvc