Java >> Java Tutorial >  >> Tag >> Spring

Hinzufügen von Social Sign-In zu einer Spring MVC-Webanwendung:Integrationstests

Ich habe über die Herausforderungen beim Schreiben von Komponententests für Anwendungen geschrieben, die Spring Social 1.1.0 verwenden, und eine Lösung dafür bereitgestellt.

Obwohl Komponententests wertvoll sind, sagen sie uns nicht wirklich, ob unsere Anwendung richtig funktioniert.

Deshalb müssen wir Integrationstests dafür schreiben .

Dieser Blogbeitrag hilft uns dabei. In diesem Blogbeitrag erfahren wir, wie wir Integrationstests für die Registrierungs- und Anmeldefunktionen unserer Beispielanwendung schreiben können.

Beginnen wir damit, einige Änderungen an der Konfiguration unseres Build-Prozesses vorzunehmen.

Konfigurieren unseres Build-Prozesses

Wir müssen die folgenden Änderungen an der Konfiguration unseres Build-Prozesses vornehmen:

  1. Wir müssen die erforderlichen Testabhängigkeiten zu unserer POM-Datei hinzufügen.
  2. Wir müssen Liquibase Changeset-Dateien zum Klassenpfad hinzufügen.

Lassen Sie uns herausfinden, wie wir diese Änderungen vornehmen können.

Erforderliche Testabhängigkeiten mit Maven erhalten

Wir können die erforderlichen Testabhängigkeiten erhalten, indem wir unserer POM-Datei die folgende Abhängigkeitsdeklaration hinzufügen:

  • Spring Test DBUnit (Version 1.1.0). Wir verwenden Spring Test DBUnit, um das Spring Test-Framework mit der DbUnit-Bibliothek zu integrieren.
  • DbUnit (Version 2.4.9). Wir verwenden DbUnit, um unsere Datenbank vor jedem Integrationstest in einen bekannten Zustand zu initialisieren und zu überprüfen, ob der Inhalt der Datenbank mit den erwarteten Daten übereinstimmt.
  • Liquibase-Core (Version 3.1.1). Wir verwenden Liquibase, um einige Datenbanktabellen zu erstellen, wenn der Anwendungskontext unserer Integrationstests geladen wird.

Der relevante Teil unserer pom.xml Datei sieht wie folgt aus:

<dependency>
	<groupId>com.github.springtestdbunit</groupId>
	<artifactId>spring-test-dbunit</artifactId>
	<version>1.1.0</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.dbunit</groupId>
	<artifactId>dbunit</artifactId>
	<version>2.4.9</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.liquibase</groupId>
	<artifactId>liquibase-core</artifactId>
	<version>3.1.1</version>
	<scope>test</scope>
</dependency>

Hinzufügen von Liquibase-Änderungssätzen zum Klassenpfad

Normalerweise sollten wir Hibernate die Datenbank erstellen lassen, die in unseren Integrationstests verwendet wird. Dieser Ansatz funktioniert jedoch nur, wenn jede Datenbanktabelle in unserem Domänenmodell konfiguriert ist.

Dies ist jetzt nicht der Fall. Die Datenbank der Beispielanwendung hat eine UserConnection Tabelle, die nicht im Domänenmodell der Beispielanwendung konfiguriert ist. Deshalb müssen wir einen anderen Weg finden, um die UserConnection zu erstellen Tabelle, bevor unsere Integrationstests ausgeführt werden.

Wir können für diesen Zweck die Spring-Integration der Liquibase-Bibliothek verwenden, aber das bedeutet, dass wir die Liquibase-Änderungssätze zum Klassenpfad hinzufügen müssen.

Wir können dies tun, indem wir das Build Helper Maven-Plugin verwenden. Wir können die Liquibase-Änderungssätze dem Klassenpfad hinzufügen, indem wir diesen Schritten folgen:

  1. Stellen Sie sicher, dass die add-test-resource Das Ziel des Builder Helper Maven-Plugins wird bei generate-test-resources aufgerufen Lebenszyklusphase.
  2. Konfigurieren Sie das Plugin, um etc/db hinzuzufügen Verzeichnis zum Klassenpfad (dieses Verzeichnis enthält die erforderlichen Dateien).

Der relevante Teil der Konfiguration des Plugins sieht wie folgt aus:

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>build-helper-maven-plugin</artifactId>
	<version>1.7</version>
	<executions>
		<!-- Other executions are omitted for the sake of clarity -->
		<execution>
			<id>add-integration-test-resources</id>
			<!-- Run this execution in the generate-test-sources lifecycle phase -->
			<phase>generate-test-resources</phase>
			<goals>
				<!-- Invoke the add-test-resource goal of this plugin -->
				<goal>add-test-resource</goal>
			</goals>
			<configuration>
				<resources>
					<!-- Other resources are omitted for the sake of clarity -->
					<!-- Add the directory which contains Liquibase change sets to classpath -->
					<resource>
						<directory>etc/db</directory>
					</resource>
				</resources>
			</configuration>
		</execution>
	</executions>
</plugin>

Wir haben nun die Konfiguration unseres Build-Prozesses abgeschlossen. Lassen Sie uns herausfinden, wie wir unsere Integrationstests konfigurieren können.

Konfigurieren unserer Integrationstests

Wir können unsere Integrationstests konfigurieren, indem wir diesen Schritten folgen:

  1. Ändern Sie die Änderungsprotokolldatei von Liquibase.
  2. Konfigurieren Sie den Anwendungskontext, um die Liquibase-Änderungssätze auszuführen, bevor unsere Testfälle aufgerufen werden.
  3. Erstellen Sie einen benutzerdefinierten DbUnit-Dataset-Loader.
  4. Konfigurieren Sie die Integrationstestfälle

Lassen Sie uns weitermachen und uns jeden Schritt genauer ansehen.

Änderung des Liquibase-Änderungsprotokolls

Unsere Beispielanwendung hat zwei Liquibase-Änderungssätze, die in etc/db/schema zu finden sind Verzeichnis. Diese Änderungssätze sind:

  1. Die db-0.0.1.sql Datei erstellt die UserConnection Tabelle, die verwendet wird, um die Verbindung des Benutzers mit dem verwendeten Social-Sign-In-Anbieter aufrechtzuerhalten.
  2. Die db-0.0.2.sql Datei erstellt die user_accounts Tabelle, die die Benutzerkonten unserer Beispielanwendung enthält.
  3. Da wir nur den ersten Änderungssatz ausführen möchten, müssen wir einige Änderungen an der Liquibase-Änderungsprotokolldatei vornehmen. Genauer gesagt müssen wir Liquibase-Kontexte verwenden, um

    anzugeben
    1. Welche Changesets werden ausgeführt, wenn wir die Datenbank unserer Beispielanwendung erstellen.
    2. Welche Änderungssätze werden ausgeführt, wenn wir unsere Integrationstests ausführen.

    Wir können unser Ziel erreichen, indem wir diesen Schritten folgen:

    1. Geben Sie an, dass die db-0.0.1.sql changeset-Datei wird ausgeführt, wenn der Liquibase-Kontext entweder db ist oder Integrationstest .
    2. Geben Sie an, dass die db-0.0.2.sql changeset-Datei wird ausgeführt, wenn der Liquibase-Kontext db ist .

    Unsere Liquibase Changelog-Datei sieht wie folgt aus:

    <?xml version="1.0" encoding="UTF-8"?>
    <databaseChangeLog
            xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://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 id="0.0.1" author="Petri" context="db,integrationtest">
            <sqlFile path="schema/db-0.0.1.sql" />
        </changeSet>
    
        <!-- Run this change set when the database is created -->
        <changeSet id="0.0.2" author="Petri" context="db">
            <sqlFile path="schema/db-0.0.2.sql" />
        </changeSet>
    </databaseChangeLog>
    

    Ausführen der Liquibase-Änderungssätze vor dem Ausführen von Integrationstests

    Wir können Liquibase-Änderungssätze ausführen, bevor unsere Integrationstests ausgeführt werden, indem wir sie ausführen, wenn der Anwendungskontext geladen wird. Wir können dies tun, indem wir diesen Schritten folgen:

    1. Erstellen Sie einen IntegrationTestContext Klasse und kommentieren Sie sie mit @Configuration Anmerkung.
    2. Fügen Sie eine Datenquelle hinzu Feld zu der erstellten Klasse und kommentieren Sie es mit @Autowired Anmerkung.
    3. Fügen Sie eine Liquibase() hinzu -Methode der Klasse hinzufügen und mit @Bean annotieren Anmerkung. Diese Methode konfiguriert die SpringLiquibase Bean, die die Liquibase-Änderungssätze ausführt, wenn der Anwendungskontext geladen wird.
    4. Implementieren Sie die liquibase() Methode, indem Sie die folgenden Schritte ausführen:
      1. Erstellen Sie eine neue SpringLiquibase Objekt.
      2. Konfigurieren Sie die vom erstellten Objekt verwendete Datenquelle.
      3. Konfigurieren Sie den Speicherort des Liquibase-Änderungsprotokolls.
      4. Setzen Sie den Liquibase-Kontext auf 'integrationtest'.
      5. Gib das erstellte Objekt zurück.

    Der Quellcode des IntegrationTestContext Klasse sieht wie folgt aus:

    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 {
    
        @Autowired
        private DataSource dataSource;
    
        @Bean
        public SpringLiquibase liquibase() {
            SpringLiquibase liquibase = new SpringLiquibase();
    
            liquibase.setDataSource(dataSource);
            liquibase.setChangeLog("classpath:changelog.xml");
            liquibase.setContexts("integrationtest");
    
            return liquibase;
        }
    }
    

    Erstellen einer benutzerdefinierten DataSetLoader-Klasse

    Der DbUnit-Datensatz, der die Informationen verschiedener Benutzerkonten enthält, sieht wie folgt aus:

    <?xml version='1.0' encoding='UTF-8'?>
    <dataset>
        <user_accounts id="1" 
                       creation_time="2014-02-20 11:13:28" 
                       email="[email protected]" 
                       first_name="Facebook" 
                       last_name="User" 
                       modification_time="2014-02-20 11:13:28" 
                       role="ROLE_USER" 
                       sign_in_provider="FACEBOOK" 
                       version="0"/>
        <user_accounts id="2" 
                       creation_time="2014-02-20 11:13:28" 
                       email="[email protected]" 
                       first_name="Twitter" 
                       last_name="User" 
                       modification_time="2014-02-20 11:13:28" 
                       role="ROLE_USER" 
                       sign_in_provider="TWITTER" 
                       version="0"/>
        <user_accounts id="3" 
                       creation_time="2014-02-20 11:13:28" 
                       email="[email protected]" 
                       first_name="RegisteredUser" 
                       last_name="User" 
                       modification_time="2014-02-20 11:13:28" 
                       password="$2a$10$PFSfOaC2IFPG.1HjO05KoODRVSdESQ5q7ek4IyzVfTf.VWlKDa/.e" 
                       role="ROLE_USER" 
                       version="0"/>
        
        <UserConnection/>
    </dataset>
    

    Aus diesem Datensatz können wir zwei Dinge erkennen:

    1. Die Benutzer, die ihr Benutzerkonto mithilfe der sozialen Anmeldung erstellt haben, haben kein Passwort.
    2. Der Benutzer, der sein Benutzerkonto mit der normalen Registrierung erstellt hat, hat ein Passwort, aber er hat keinen Anmeldeanbieter.

    Dies ist ein Problem, da wir sogenannte flache XML-Datensätze verwenden und der standardmäßige DbUnit-Datensatzlader diese Situation nicht bewältigen kann. Wir könnten natürlich anfangen, die Standard-XML-Datensätze zu verwenden, aber die Syntax ist für meinen Geschmack etwas zu ausführlich. Aus diesem Grund müssen wir einen benutzerdefinierten Dataset-Loader erstellen, der mit dieser Situation umgehen kann.

    Wir können einen benutzerdefinierten Dataset-Loader erstellen, indem wir diesen Schritten folgen:

    1. Erstellen Sie einen ColumnSensingFlatXMLDataSetLoader Klasse, die den AbstractDataSetLoader erweitert Klasse.
    2. Überschreiben Sie das createDataSet() -Methode und implementieren Sie sie, indem Sie die folgenden Schritte ausführen:
      1. Erstellen Sie einen neuen FlatXmlDataSetBuilder Objekt.
      2. Aktivieren Sie die Spaltenerkennung. Column Sensing bedeutet, dass DbUnit den gesamten Datensatz aus der Datensatzdatei liest und neue Spalten hinzufügt, wenn sie im Datensatz gefunden werden. Dadurch wird sichergestellt, dass der Wert jeder Spalte korrekt in die Datenbank eingefügt wird.
      3. Erstellen Sie ein neues IDataSet Objekt und gibt das erstellte Objekt zurück.

    Der Quellcode des ColumnSensingFlatXMLDataSetLoader Klasse sieht wie folgt aus:

    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 {
        @Override
        protected 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();
            }
        }
    }
    

    Das Erstellen einer benutzerdefinierten Dataset-Loader-Klasse reicht jedoch nicht aus. Wir müssen unsere Tests noch konfigurieren, um diese Klasse zu verwenden, wenn unsere Datensätze geladen werden. Wir können dies tun, indem wir die Testklasse mit @DbUnitConfiguration kommentieren -Anmerkung und Festlegen des Werts ihres dataSetLoader -Attribut zu ColumnSensingFlatXMLDataSetLoader.class .

    Sehen wir uns an, wie das gemacht wird.

    Konfigurieren unserer Integrationstests

    Wir können unsere Integrationstests konfigurieren, indem wir diesen Schritten folgen:

    1. Stellen Sie sicher, dass Tests von Spring SpringJUnit4ClassRunner ausgeführt werden . Wir können dies tun, indem wir die Testklasse mit @RunWith kommentieren -Anmerkung und Festlegen ihres Werts auf SpringJUnit4ClassRunner.class .
    2. Laden Sie den Anwendungskontext, indem Sie die Testklasse mit @ContextConfiguration kommentieren Anmerkung, und konfigurieren Sie die Konfigurationsklassen oder -dateien des verwendeten Anwendungskontexts.
    3. Kommentieren Sie die Testklasse mit @WebAppConfiguration Anmerkung. Dadurch wird sichergestellt, dass der für unsere Integrationstests geladene Anwendungskontext ein WebApplicationContext ist .
    4. Annotieren Sie die Klasse mit den @TestExecutionListeners -Anmerkung und übergeben Sie die Standard-Spring-Listener und den DBUnitTestExecutionListener als seinen Wert. Der DBUnitTestExecutionListener stellt sicher, dass Spring die in unserer Testklasse gefundenen DbUnit-Anmerkungen verarbeitet.
    5. Konfigurieren Sie die Testklasse, um unseren benutzerdefinierten Dataset-Loader zu verwenden, indem Sie die Testklasse mit @DbUnitConfiguration kommentieren Anmerkung. Legen Sie den Wert seines dataSetLoader fest -Attribut zu ColumnSensingFlatXMLDataSetLoader.class .
    6. Fügen Sie einen FilterChainProxy hinzu -Feld in die Testklasse und kommentieren Sie das Feld mit der Annotation @Autowired.
    7. Fügen Sie einen WebApplicationContext hinzu Feld zur Testklasse und kommentieren Sie das Feld mit @Autowired Anmerkung.
    8. Fügen Sie einen MockMvc hinzu Feld in die Testklasse.
    9. Fügen Sie ein setUp() hinzu -Methode in die Testklasse und kommentieren Sie diese Methode mit @Before Anmerkung, die sicherstellt, dass diese Methode vor jeder Testmethode aufgerufen wird.
    10. Implementieren Sie setUp() -Methode und erstellen Sie einen neuen MockMvc -Objekt mithilfe von MockMvcBuilders Klasse.

    Der Quellcode einer leeren Testklasse sieht wie folgt aus:

    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 {
    
        @Autowired
        private FilterChainProxy springSecurityFilterChain;
    
        @Autowired
        private WebApplicationContext webApplicationContext;
    
        private MockMvc mockMvc;
    
        @Before
        public void setUp() {
            mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
                    .addFilter(springSecurityFilterChain)
                    .build();
        }
    }
    

    Wir haben nun gelernt, wie wir unsere Integrationstests konfigurieren können. Fahren wir fort und erstellen einige Testdienstprogrammklassen, die in unseren Integrationstests verwendet werden.

    Erstellen von Testdienstprogrammklassen

    Als nächstes erstellen wir drei Utility-Klassen, die in unseren Integrationstests verwendet werden:

    1. Wir erstellen die IntegrationTestConstants Klasse, die die Konstanten enthält, die in mehr als einem Integrationstest verwendet werden.
    2. Wir werden die Klassen erstellen, die zum Erstellen von ProviderSignInAttempt verwendet werden Objekte für unsere Integrationstests.
    3. Wir erstellen eine Test-Data-Builder-Klasse, die zum Erstellen von CsrfToken verwendet wird Objekte.

    Lassen Sie uns herausfinden, warum wir diese Klassen erstellen müssen und wie wir sie erstellen können.

    Erstellen der IntegrationTestConstants-Klasse

    Wenn wir Integrationstests (oder Komponententests) schreiben, müssen wir manchmal dieselben Informationen in vielen Testklassen verwenden. Das Duplizieren dieser Informationen in alle Testklassen ist keine gute Idee, da unsere Tests dadurch schwieriger zu warten und zu verstehen sind. Stattdessen sollten wir diese Informationen einer einzigen Klasse zuweisen und sie von dieser Klasse abrufen, wenn wir sie brauchen.

    Die IntegrationTestConstants Klasse enthält die folgenden Informationen, die in mehr als einer Testklasse verwendet werden:

    • Es enthält die Konstanten, die sich auf den CSRF-Schutz von Spring Security 3.2 beziehen. Zu diesen Konstanten gehören:der Name des HTTP-Headers, der das CSRF-Token enthält, der Name des Anforderungsparameters, der den Wert des CSRF-Tokens enthält, der Name des Sitzungsattributs, das das CsrfToken enthält -Objekt und den Wert des CSRF-Tokens.
    • Es enthält den Benutzer enum, die die in unserem Integrationstest verwendeten Benutzer angibt. Jeder Benutzer hat einen Benutzernamen und ein Passwort (dies ist nicht erforderlich). Die Informationen dieser Aufzählung werden für zwei Zwecke verwendet:
      1. Es wird verwendet, um den angemeldeten Benutzer anzugeben. Dies ist nützlich, wenn wir Integrationstests für geschützte Funktionen durchführen (Funktionen, die eine Art Autorisierung erfordern).
      2. Wenn wir Integrationstests für die Anmeldefunktion schreiben, müssen wir den Benutzernamen und das Passwort des Benutzers angeben, der versucht, sich bei der Anwendung anzumelden.

    Der Quellcode der IntegrationTestConstants Klasse sieht wie folgt aus:

    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("[email protected]", null),
            REGISTERED_USER("[email protected]", "password"),
            TWITTER_USER("[email protected]", 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;
            }
        }
    }
    

    Erstellen von ProviderSignInAttempt-Objekten

    Als wir Komponententests für unsere Beispielanwendung geschrieben haben, haben wir einen kurzen Blick auf die Klasse ProviderSignInUtils geworfen und festgestellt, dass wir einen Weg finden müssen, ProviderSignInAttempt zu erstellen Objekte.

    Wir haben dieses Problem gelöst, indem wir eine Stub-Klasse erstellt haben, die in unseren Komponententests verwendet wurde. Diese Stub-Klasse gibt uns die Möglichkeit, die zurückgegebene Verbindung zu konfigurieren Objekt und um zu überprüfen, ob eine bestimmte Verbindung mit der Datenbank "bestanden" wurde. Unsere Stub-Klasse hat jedoch keine Verbindungen zur verwendeten Datenbank beibehalten. Stattdessen wurde die Benutzer-ID des Benutzers in einem Set gespeichert Objekt.

    Da wir nun Verbindungsdaten zur Datenbank persistieren wollen, müssen wir Änderungen an unserer Stub-Klasse vornehmen. Wir können diese Änderungen vornehmen, indem wir diese Änderungen am TestProviderSignInAttempt vornehmen Objekt:

    1. Fügen Sie ein privates usersConnectionRepositorySet hinzu Feld zum TestProviderSignInAttempt Klasse. Der Typ dieses Feldes ist boolean und sein Standardwert ist falsch. Dieses Feld beschreibt, ob wir Verbindungen zum verwendeten Datenspeicher beibehalten können.
    2. Fügen Sie dem Konstruktor von TestProviderSignInAttempt ein neues Konstruktorargument hinzu Klasse. Der Typ dieses Arguments ist UsersConnectionRepository und es wird verwendet, um Verbindungen zum verwendeten Datenspeicher aufrechtzuerhalten.
    3. Implementieren Sie den Konstruktor, indem Sie diesen Schritten folgen:
      1. Rufen Sie den Konstruktor der Superklasse auf und übergeben Sie die Connection und UsersConnectionRepository Objekte als Konstruktorargumente.
      2. Speichern Sie einen Verweis auf die Verbindung Objekt, das als Konstruktorargument an die Verbindung übergeben wird Feld.
      3. Wenn das UsersConnectionRepository Wenn das als Konstruktorargument angegebene Objekt nicht null ist, legen Sie den Wert von usersConnectionRepositoryField fest zu wahr.
    4. Implementieren Sie die Methode addConnection(), indem Sie diesen Schritten folgen:
      1. Fügen Sie die als Methodenparameter angegebene Benutzer-ID zu den Verbindungen hinzu Einstellen .
      2. Wenn das UsersConnectionRepository -Objekt wurde bei einem neuen TestProviderSignInAttempt gesetzt Objekt erstellt wurde, rufen Sie addConnection() auf Methode des ProviderSignInAttempt Klasse und übergeben Sie die Benutzer-ID als Methodenparameter.

    Der Quellcode des TestProviderSignInAttempt Klasse sieht wie folgt aus (die geänderten Teile sind hervorgehoben):

    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 Set<String> 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;
            }
        }
    
        @Override
        public Connection<?> getConnection() {
            return connection;
        }
    
        @Override
        void addConnection(String userId) {
            connections.add(userId);
            if (usersConnectionRepositorySet) {
                super.addConnection(userId);
            }
        }
    
        public Set<String> getConnections() {
            return connections;
        }
    }
    

    Weil wir neue TestProviderSignInAttempt bauen Objekte mithilfe des TestProviderSignInAttemptBuilder , müssen wir auch an dieser Klasse Änderungen vornehmen. Wir können diese Änderungen vornehmen, indem wir diesen Schritten folgen:

    1. Fügen Sie ein privates usersConnectionRepository hinzu Feld zum TestProviderSignInAttemptBuilder Klasse und setzen Sie ihren Typ auf UsersConnectionRepository .
    2. Fügen Sie ein usersConnectionRepository() hinzu Methode zur Klasse. Setzen Sie einen Verweis auf UsersConnectionRepository Objekt zum usersConnectionRepository -Feld und geben einen Verweis auf das Builder-Objekt zurück.
    3. Ändern Sie die letzte Zeile von build() -Methode und erstellen Sie einen neuen TestProviderSignInAttempt Objekt, indem Sie den neuen Konstruktor verwenden, den wir zuvor erstellt haben.

    Der Quellcode des TestProviderSignInAttemptBuilder Klasse sieht wie folgt aus (die geänderten Teile sind hervorgehoben):

    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);
        }
    }
    

    Wir können einen neuen TestProviderSignInAttempt erstellen Objekte mithilfe des folgenden Codes:

    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("[email protected]")
    		.firstName("John")
    		.lastName("Smith")
    	.build();
    

    CsrfToken-Objekte erstellen

    Da unsere Beispielanwendung den von Spring Security 3.2 bereitgestellten CSRF-Schutz verwendet, müssen wir in unseren Integrationstests einen Weg finden, gültige CSRF-Token zu erstellen. Das CsrfToken interface deklariert die Methoden, die Informationen über das erwartete CSRF-Token liefern. Diese Schnittstelle hat eine Implementierung namens DefaultCsrfToken .

    Mit anderen Worten, wir müssen einen Weg finden, ein neues DefaultCsrfToken zu erstellen Objekte. Das DefaultCsrfToken Klasse hat einen einzigen Konstruktor, und wir könnten ihn natürlich verwenden, wenn wir ein neues DefaultCsrfToken erstellen Objekte in unseren Integrationstests. Das Problem ist, dass dies nicht sehr lesbar ist.

    Stattdessen erstellen wir eine Test-Data-Builder-Klasse, die eine fließende API zum Erstellen neuer CsrfToken bereitstellt Objekte. Wir können diese Klasse erstellen, indem wir diesen Schritten folgen:

    1. Erstellen Sie einen CsrfTokenBuilder Klasse.
    2. Fügen Sie einen privaten headerName hinzu Feld zur erstellten Klasse.
    3. Fügen Sie einen privaten requestParameterName hinzu Feld zur erstellten Klasse.
    4. Fügen Sie einen privaten tokenValue hinzu Feld zur erstellten Klasse.
    5. Fügen Sie der erstellten Klasse einen Veröffentlichungskonstruktor hinzu.
    6. Fügen Sie die Methoden hinzu, die zum Festlegen der Feldwerte von headerName verwendet werden , requestParameterName und tokenValue Felder.
    7. Fügen Sie einen Build() hinzu -Methode auf die erstellte Klasse und setzen Sie ihren Rückgabetyp auf CsrfToken . Implementieren Sie diese Methode, indem Sie die folgenden Schritte ausführen:
      1. Erstellen Sie ein neues DefaultCsrfToken -Objekt und geben Sie den Namen des CSRF-Token-Headers, den Namen des CSRF-Token-Anforderungsparameters und den Wert des CSRF-Tokens als Konstruktorargumente an.
      2. Gib das erstellte Objekt zurück.

    Der Quellcode des CsrfTokenBuilder Klasse sieht wie folgt aus:

    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);
        }
    }
    

    Wir können neue CsrfToken erstellen Objekte mit diesem Code:

    CsrfToken csrfToken = new CsrfTokenBuilder()
    		.headerName(IntegrationTestConstants.CSRF_TOKEN_HEADER_NAME)
    		.requestParameterName(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME)
    		.tokenValue(IntegrationTestConstants.CSRF_TOKEN_VALUE)
    		.build();
    

    Wir haben jetzt die erforderlichen Testdienstprogrammklassen erstellt. Fahren wir fort und beginnen mit dem Schreiben von Integrationstests für unsere Beispielanwendung.

    Integrationstests schreiben

    Wir sind endlich bereit, einige Integrationstests für unsere Beispielanwendung zu schreiben. Wir werden die folgenden Integrationstests schreiben:

    • Wir werden Integrationstests schreiben, die sicherstellen, dass der Formular-Login korrekt funktioniert.
    • Wir werden Integrationstests schreiben, die überprüfen, ob die Registrierung korrekt funktioniert, wenn die soziale Anmeldung verwendet wird.

    Aber bevor wir mit dem Schreiben dieser Integrationstests beginnen, werden wir lernen, wie wir Spring Security gültige CSRF-Token bereitstellen können.

    Gültige CSRF-Token für Spring Security bereitstellen

    Zuvor haben wir gelernt, wie wir CsrfToken erstellen können Objekte in unseren Integrationstests. Wir müssen jedoch noch einen Weg finden, diese CSRF-Token Spring Security bereitzustellen.

    Es ist an der Zeit, einen genaueren Blick darauf zu werfen, wie Spring Security mit CSRF-Token umgeht.

    Das CsrfTokenRepository interface deklariert die Methoden, die zum Generieren, Speichern und Laden von CSRF-Tokens erforderlich sind. Die Standardimplementierung dieser Schnittstelle ist das HttpSessionCsrfTokenRepository Klasse, die CSRF-Token in der HTTP-Sitzung speichert.

    Wir müssen die Antworten auf die folgenden Fragen finden:

    • Wie werden die CSRF-Token in der HTTP-Sitzung gespeichert?
    • Wie werden die CSRF-Token aus der HTTP-Sitzung geladen?

    Antworten auf diese Fragen finden wir, wenn wir uns den Quellcode des HttpSessionCsrfTokenRepository ansehen Klasse. Der relevante Teil des HttpSessionCsrfTokenRepository Klasse sieht wie folgt aus:

    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.
    }
    

    Es ist nun klar, dass die CSRF-Token in der HTTP-Sitzung als CsrfToken gespeichert werden Objekte, und diese Objekte werden erneut versucht und gespeichert, indem der Wert von sessionAttributeName verwendet wird Eigentum. Das bedeutet, wenn wir Spring Security ein gültiges CSRF-Token bereitstellen möchten, müssen wir die folgenden Schritte ausführen:

    1. Erstellen Sie ein neues CsrfToken Objekt mit unserem Test Data Builder.
    2. Senden Sie den Wert des CSRF-Tokens als Anfrageparameter.
    3. Speichern Sie das erstellte DefaultCsrfToken Objekt für die HTTP-Sitzung, sodass das HttpSessionCsrfTokenRepository findet es.

    Der Quellcode unseres Dummy-Tests sieht wie folgt aus:

    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;
    
        @Test
        public void test() throws Exception {
    		//1. Create a new CSRF token
            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")
    				//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.
        }
    }
    

    Genug der Theorie. Wir sind jetzt bereit, einige Integrationstests für unsere Anwendung zu schreiben. Beginnen wir damit, die Integration in die Anmeldefunktion unserer Beispielanwendung zu schreiben.

    Schreiben von Tests für die Login-Funktion

    Es ist an der Zeit, Integrationstests für die Login-Funktion unserer Beispielanwendung zu schreiben. Wir werden die folgenden Integrationstests dafür schreiben:

    1. Wir schreiben einen Integrationstest, der sicherstellt, dass bei erfolgreicher Anmeldung alles wie erwartet funktioniert.
    2. Wir werden einen Integrationstest schreiben, der sicherstellt, dass alles funktioniert, wenn die Anmeldung fehlschlägt.

    Beide Integrationstests initialisieren die Datenbank in einen bekannten Zustand, indem sie dieselbe DbUnit-Datensatzdatei (users.xml) verwenden ) und sein Inhalt sieht wie folgt aus:

    <?xml version='1.0' encoding='UTF-8'?>
    <dataset>
        <user_accounts id="1" 
                       creation_time="2014-02-20 11:13:28" 
                       email="[email protected]" 
                       first_name="Facebook" 
                       last_name="User" 
                       modification_time="2014-02-20 11:13:28" 
                       role="ROLE_USER" 
                       sign_in_provider="FACEBOOK" 
                       version="0"/>
        <user_accounts id="2" 
                       creation_time="2014-02-20 11:13:28" 
                       email="[email protected]" 
                       first_name="Twitter" 
                       last_name="User" 
                       modification_time="2014-02-20 11:13:28" 
                       role="ROLE_USER" 
                       sign_in_provider="TWITTER" 
                       version="0"/>
        <user_accounts id="3" 
                       creation_time="2014-02-20 11:13:28" 
                       email="[email protected]" 
                       first_name="RegisteredUser" 
                       last_name="User" 
                       modification_time="2014-02-20 11:13:28" 
                       password="$2a$10$PFSfOaC2IFPG.1HjO05KoODRVSdESQ5q7ek4IyzVfTf.VWlKDa/.e" 
                       role="ROLE_USER" 
                       version="0"/>
        
        <UserConnection/>
    </dataset>
    

    Fangen wir an.

    Test 1:Anmeldung erfolgreich

    Wir können den ersten Integrationstest schreiben, indem wir diesen Schritten folgen:

    1. Kommentieren Sie die Testklasse mit @DatabaseSetup Anmerkung und konfigurieren Sie das Dataset, das verwendet wird, um die Datenbank in einen bekannten Zustand zu initialisieren, bevor der Integrationstest aufgerufen wird.
    2. Erstellen Sie ein neues CsrfToken Objekt.
    3. Senden Sie eine POST-Anforderung an die URL „/login/authenticate“, indem Sie die folgenden Schritte ausführen:
      1. Legen Sie die Werte von Benutzername fest und Passwort Anfrageparameter. Verwenden Sie das richtige Passwort.
      2. Setzen Sie den Wert des CSRF-Tokens auf die Anfrage.
      3. Setzen Sie das erstellte CsrfToken auf session.
    4. Stellen Sie sicher, dass der HTTP-Statuscode 302 zurückgegeben wird.
    5. Stellen Sie sicher, dass die Anfrage an die URL „/“ umgeleitet wird.

    Der Quellcode unseres Integrationstests sieht wie folgt aus:

    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 clarity
    
        private MockMvc mockMvc;
    
        //The setUp() method is omitted for the sake of clarify.
    
        @Test
        public 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("/"));
        }
    }
    

    Test 2:Anmeldung schlägt fehl

    Wir können den zweiten Integrationstest schreiben, indem wir diesen Schritten folgen:

    1. Kommentieren Sie die Testklasse mit @DatabaseSetup Anmerkung und konfigurieren Sie das Dataset, das verwendet wird, um die Datenbank in einen bekannten Zustand zu initialisieren, bevor der Integrationstest aufgerufen wird.
    2. Erstellen Sie ein neues CsrfToken Objekt.
    3. Senden Sie eine POST-Anforderung an die URL „/login/authenticate“, indem Sie die folgenden Schritte ausführen:
      1. Werte für Benutzername festlegen und Passwort Anfrageparameter. Falsches Passwort verwenden.
      2. Legen Sie den Wert des CSRF-Tokens als Anfrageparameter für die Anfrage fest.
      3. Legen Sie das erstellte CsrfToken fest Sitzung widersprechen.
    4. Stellen Sie sicher, dass der HTTP-Statuscode 302 zurückgegeben wird.
    5. Vergewissern Sie sich, dass die Anfrage an die URL „/login?error=bad_credentials“ umgeleitet wird.

    Der Quellcode unseres Integrationstests sieht wie folgt aus:

    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 clarity
    
        private MockMvc mockMvc;
    
        //The setUp() method is omitted for the sake of clarify.
    
        @Test
        public 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?error=bad_credentials"));
        }
    }
    

    Schreibtests für die Registrierungsfunktion

    Wir werden die folgenden Integrationstests für die Registrierungsfunktion schreiben:

    1. Wir werden einen Integrationstest schreiben, der sicherstellt, dass unsere Anwendung ordnungsgemäß funktioniert, wenn der Benutzer ein neues Benutzerkonto erstellt, indem er die soziale Anmeldung verwendet, aber die Validierung des übermittelten Registrierungsformulars fehlschlägt.
    2. Wir werden einen Integrationstest schreiben, der überprüft, ob alles richtig funktioniert, wenn der Benutzer ein neues Benutzerkonto erstellt, indem er die soziale Anmeldung und eine E-Mail-Adresse verwendet, die aus der Datenbank gefunden wird.
    3. Wir werden einen Integrationstest schreiben, der sicherstellt, dass es möglich ist, ein neues Benutzerkonto mithilfe der sozialen Anmeldung zu erstellen.

    Fangen wir an.

    Test 1:Validierung schlägt fehl

    Wir können den ersten Integrationstest schreiben, indem wir diesen Schritten folgen:

    1. Fügen Sie ein UsersConnectionRepository hinzu Feld zur Testklasse und kommentieren Sie es mit @Autowired Anmerkung.
    2. Kommentieren Sie die Testmethode mit @DatabaseSetup Anmerkung und konfigurieren Sie den Datensatz, der verwendet wird, um die Datenbank in einen bekannten Zustand zu initialisieren, bevor unser Integrationstest ausgeführt wird.
    3. Erstellen Sie einen neuen TestProviderSignInAttempt Objekt. Denken Sie daran, das verwendete UsersConnectionRepository zu setzen Objekt.
    4. Erstellen Sie ein neues CsrfToken Objekt.
    5. Senden Sie eine POST-Anforderung an die URL „/user/register“, indem Sie die folgenden Schritte ausführen:
      1. Setzen Sie den Inhaltstyp der Anfrage auf „application/x-www-form-urlencoded“.
      2. Senden Sie den Wert des signInProvider Formularfeld als Anfrageparameter.
      3. Legen Sie den Wert des CSRF-Tokens als Anfrageparameter für die Anfrage fest.
      4. Legen Sie das erstellte CsrfToken fest Sitzung widersprechen.
      5. Legen Sie den erstellten TestProviderSignInAttempt fest Sitzung widersprechen.
      6. Legen Sie ein neues Registrierungsformular fest Sitzung widersprechen. Dies ist erforderlich, da unsere Controller-Klasse mit @SessionAttributes annotiert ist Anmerkung.
    6. Stellen Sie sicher, dass der HTTP-Anforderungsstatus 200 zurückgegeben wird.
    7. Stellen Sie sicher, dass der Name der gerenderten Ansicht „user/registrationForm“ lautet.
    8. Vergewissern Sie sich, dass die Anfrage an die URL „/WEB-INF/jsp/user/registrationForm.jsp“ weitergeleitet wird.
    9. Vergewissern Sie sich, dass die Felder des Modellattributs mit dem Namen "Benutzer" korrekt sind.
    10. Stellen Sie sicher, dass das Modellattribut namens "Benutzer" Feldfehler in E-Mail aufweist , Vorname und Nachname Felder.
    11. Kommentieren Sie die Testmethode mit @ExpectedDatabase Anmerkung und stellen Sie sicher, dass kein neues Benutzerkonto in der Datenbank gespeichert wurde (verwenden Sie denselben Datensatz, der zum Initialisieren der Datenbank verwendet wurde).

    Der Quellcode unseres Integrationstests sieht wie folgt aus:

    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 {
        
        @Autowired
        private UsersConnectionRepository usersConnectionRepository;
    
        //Some fields are omitted for the sake of clarity.
    
        private MockMvc mockMvc;
    
        //The setUp() is omitted for the sake of clarity.
    
        @Test
        @DatabaseSetup("no-users.xml")
        @ExpectedDatabase(value="no-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("[email protected]")
                        .firstName("John")
                        .lastName("Smith")
                    .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)
    				.param("signInProvider", SocialMediaService.TWITTER.name())
                    .param(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME, IntegrationTestConstants.CSRF_TOKEN_VALUE)
                    .sessionAttr(IntegrationTestConstants.CSRF_TOKEN_SESSION_ATTRIBUTE_NAME, csrfToken)
    				.sessionAttr(ProviderSignInAttempt.SESSION_ATTRIBUTE, socialSignIn)
                    .sessionAttr("user", new RegistrationForm())
            )
                    .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"));
        }
    }
    

    Unser Integrationstest verwendet eine DbUnit-Datensatzdatei namens no-users.xml die wie folgt aussieht:

    <?xml version='1.0' encoding='UTF-8'?>
    <dataset>
        <user_accounts/>
        <UserConnection/>
    </dataset>
    

    Test 2:E-Mail-Adresse wird aus der Datenbank gefunden

    Wir können den zweiten Integrationstest schreiben, indem wir diesen Schritten folgen:

    1. Fügen Sie ein UsersConnectionRepository hinzu Feld zur Testklasse und kommentieren Sie es mit @Autowired Anmerkung.
    2. Kommentieren Sie die Testmethode mit @DatabaseSetup Anmerkung und konfigurieren Sie den Datensatz, der verwendet wird, um die Datenbank in einen bekannten Zustand zu initialisieren, bevor unser Integrationstest ausgeführt wird.
    3. Erstellen Sie einen neuen TestProviderSignInAttempt Objekt. Denken Sie daran, das verwendete UsersConnectionRepository-Objekt zu setzen.
    4. Erstellen Sie ein neues CsrfToken Objekt.
    5. Senden Sie eine POST-Anforderung an die URL „/user/register“, indem Sie die folgenden Schritte ausführen:
      1. Setzen Sie den Inhaltstyp der Anfrage auf „application/x-www-form-urlencoded“.
      2. Senden Sie die Werte der E-Mail , Vorname , Nachname und signInProvider Formularfelder als Anfrageparameter. Verwenden Sie eine vorhandene E-Mail-Adresse.
      3. Legen Sie den Wert des CSRF-Tokens als Anfrageparameter für die Anfrage fest.
      4. Legen Sie das erstellte CsrfToken fest Sitzung widersprechen.
      5. Legen Sie den erstellten TestProviderSignInAttempt fest Sitzung widersprechen.
      6. Legen Sie ein neues Registrierungsformular fest Sitzung widersprechen. Dies ist erforderlich, da unsere Controller-Klasse mit @SessionAttributes annotiert ist Anmerkung.
    6. Stellen Sie sicher, dass der HTTP-Anforderungsstatus 200 zurückgegeben wird.
    7. Stellen Sie sicher, dass der Name der gerenderten Ansicht „user/registrationForm“ lautet.
    8. Vergewissern Sie sich, dass die Anfrage an die URL „/WEB-INF/jsp/user/registrationForm.jsp“ weitergeleitet wird.
    9. Vergewissern Sie sich, dass die Felder des Modellattributs mit dem Namen "Benutzer" korrekt sind.
    10. Stellen Sie sicher, dass das Modellattribut mit dem Namen "Benutzer" einen Feldfehler in E-Mail aufweist Feld.
    11. Kommentieren Sie die Testmethode mit @ExpectedDatabase Anmerkung und stellen Sie sicher, dass kein neues Benutzerkonto in der Datenbank gespeichert wurde (verwenden Sie denselben Datensatz, der zum Initialisieren der Datenbank verwendet wurde).

    Der Quellcode unseres Integrationstests sieht wie folgt aus:

    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 {
    
        @Autowired
        private UsersConnectionRepository usersConnectionRepository;
    
        //Some fields are omitted for the sake of clarity.
    
        private MockMvc mockMvc;
    
        //The setUp() is omitted for the sake of clarity.
    
        @Test
        @DatabaseSetup("/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();
    
            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)
    				.param("email", IntegrationTestConstants.User.REGISTERED_USER.getUsername())
    				.param("firstName", "John")
    				.param("lastName", "Smith")
    				.param("signInProvider", SociaMediaService.TWITTER.name())
                    .param(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME, IntegrationTestConstants.CSRF_TOKEN_VALUE)
                    .sessionAttr(IntegrationTestConstants.CSRF_TOKEN_SESSION_ATTRIBUTE_NAME, csrfToken)
    				.sessionAttr(ProviderSignInAttempt.SESSION_ATTRIBUTE, socialSignIn)
                    .sessionAttr("user", new RegistrationForm())
            )
                    .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"));
        }
    }
    

    Dieser Integrationstest verwendet einen DbUnit-Datensatz namens users.xml die wie folgt aussieht:

    <?xml version='1.0' encoding='UTF-8'?>
    <dataset>
        <user_accounts id="1" 
                       creation_time="2014-02-20 11:13:28" 
                       email="[email protected]" 
                       first_name="Facebook" 
                       last_name="User" 
                       modification_time="2014-02-20 11:13:28" 
                       role="ROLE_USER" 
                       sign_in_provider="FACEBOOK" 
                       version="0"/>
        <user_accounts id="2" 
                       creation_time="2014-02-20 11:13:28" 
                       email="[email protected]" 
                       first_name="Twitter" 
                       last_name="User" 
                       modification_time="2014-02-20 11:13:28" 
                       role="ROLE_USER" 
                       sign_in_provider="TWITTER" 
                       version="0"/>
        <user_accounts id="3" 
                       creation_time="2014-02-20 11:13:28" 
                       email="[email protected]" 
                       first_name="RegisteredUser" 
                       last_name="User" 
                       modification_time="2014-02-20 11:13:28" 
                       password="$2a$10$PFSfOaC2IFPG.1HjO05KoODRVSdESQ5q7ek4IyzVfTf.VWlKDa/.e" 
                       role="ROLE_USER" 
                       version="0"/>
        
        <UserConnection/>
    </dataset>
    

    Test 3:Die Registrierung ist erfolgreich

    Wir können den dritten Integrationstest schreiben, indem wir diesen Schritten folgen:

    1. Fügen Sie ein UsersConnectionRepository hinzu Feld zur Testklasse und kommentieren Sie es mit @Autowired Anmerkung.
    2. Kommentieren Sie die Testmethode mit @DatabaseSetup Anmerkung und konfigurieren Sie den Datensatz, der verwendet wird, um die Datenbank in einen bekannten Zustand zu initialisieren, bevor unser Integrationstest ausgeführt wird.
    3. Erstellen Sie einen neuen TestProviderSignInAttempt Objekt. Denken Sie daran, das verwendete UsersConnectionRepository zu setzen Objekt.
    4. Erstellen Sie ein neues CsrfToken Objekt.
    5. Senden Sie eine POST-Anforderung an die URL „/user/register“, indem Sie die folgenden Schritte ausführen:
      1. Setzen Sie den Inhaltstyp der Anfrage auf „application/x-www-form-urlencoded“.
      2. Senden Sie die Werte der E-Mail , Vorname , Nachname und signInProvider Formularfelder als Anfrageparameter.
      3. Legen Sie den Wert des CSRF-Tokens als Anfrageparameter für die Anfrage fest.
      4. Legen Sie das erstellte CsrfToken fest Sitzung widersprechen.
      5. Legen Sie den erstellten TestProviderSignInAttempt fest Sitzung widersprechen.
      6. Legen Sie ein neues Registrierungsformular fest Sitzung widersprechen. Dies ist erforderlich, da unsere Controller-Klasse mit @SessionAttributes annotiert ist Anmerkung.
    6. Stellen Sie sicher, dass der HTTP-Anforderungsstatus 302 zurückgegeben wird.
    7. Stellen Sie sicher, dass die Anfrage an die URL „/“ umgeleitet wird. Dadurch wird auch sichergestellt, dass der erstellte Benutzer angemeldet ist, da anonyme Benutzer nicht auf diese URL zugreifen können.
    8. Kommentieren Sie die Testmethode mit @ExpectedDatabase Anmerkung und stellen Sie sicher, dass ein neues Benutzerkonto in einer Datenbank gespeichert wurde und die Verbindung zum verwendeten Social-Media-Anbieter bestehen blieb.

    Der Quellcode unseres Integrationstests sieht wie folgt aus:

    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 {
        
        @Autowired
        private UsersConnectionRepository usersConnectionRepository;
    
        //Some fields are omitted for the sake of clarity.
    
        private MockMvc mockMvc;
    
        //The setUp() is omitted for the sake of clarity.
    
        @Test
        @DatabaseSetup("no-users.xml")
        @ExpectedDatabase(value="register-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("[email protected]")
                        .firstName("John")
                        .lastName("Smith")
                    .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)
    				.param("email", "[email protected]")
    				.param("firstName", "John")
    				.param("lastName", "Smith")
    				.param("signInProvider", SociaMediaService.TWITTER.name())
                    .param(IntegrationTestConstants.CSRF_TOKEN_REQUEST_PARAM_NAME, IntegrationTestConstants.CSRF_TOKEN_VALUE)
                    .sessionAttr(IntegrationTestConstants.CSRF_TOKEN_SESSION_ATTRIBUTE_NAME, csrfToken)
    				.sessionAttr(ProviderSignInAttempt.SESSION_ATTRIBUTE, socialSignIn)
                    .sessionAttr("user", new RegistrationForm())
            )
                    .andExpect(status().isMovedTemporarily())
                    .andExpect(redirectedUrl("/"));
        }
    } 
    

    Der Datensatz (no-users.xml ), die verwendet wird, um die Datenbank in einen bekannten Zustand zu initialisieren, sieht wie folgt aus:

    <?xml version='1.0' encoding='UTF-8'?>
    <dataset>
        <user_accounts/>
        <UserConnection/>
    </dataset>
    

    Der DbUnit-Datensatz namens register-social-user-expected.xml wird verwendet, um zu überprüfen, ob das Benutzerkonto erfolgreich erstellt und die Verbindung zum verwendeten Social-Sign-In-Anbieter in der Datenbank beibehalten wurde. Es sieht wie folgt aus:

    <?xml version='1.0' encoding='UTF-8'?>
    <dataset>
        <user_accounts email="[email protected]" 
                       first_name="John" last_name="Smith" 
                       role="ROLE_USER" 
                       sign_in_provider="TWITTER"
                       version="0"/>
    
        <UserConnection userId="[email protected]"
                        providerId="twitter"
                        providerUserId="johnsmith"
                        rank="1"
                        displayName="John Smith"
                        profileUrl="https://www.twitter.com/johnsmith"
                        imageUrl="https://www.twitter.com/images/johnsmith.jpg"
                        accessToken="accessToken"
                        secret="secret"
                        refreshToken="refreshToken"
                        expireTime="100000"/>
    </dataset>
    

    Zusammenfassung

    Wir haben jetzt gelernt, wie wir Integrationstests für eine normale Spring MVC-Anwendung schreiben können, die Spring Social 1.1.0 verwendet. Dieses Tutorial hat uns viele Dinge beigebracht, aber diese beiden Dinge sind die wichtigsten Lektionen dieses Blogposts:

    • Wir haben gelernt, wie wir die soziale Anmeldung "simulieren" können, indem wir ProviderSignInAttempt-Objekte erstellen und sie in unseren Integrationstests verwenden.
    • Wir haben gelernt, wie wir CSRF-Token erstellen und Spring Security die erstellten Token zur Verfügung stellen können.

    Nehmen wir uns einen Moment Zeit und analysieren die Vor- und Nachteile des in diesem Blogbeitrag beschriebenen Ansatzes:

    Vorteile:

    • Wir können Integrationstests schreiben, ohne einen externen Social-Sign-In-Anbieter zu verwenden. Das macht unsere Tests weniger spröde und einfacher zu warten.
    • Die Implementierungsdetails von Spring Social (ProviderSignInAttempt ) und Spring Security CSRF-Schutz (CsrfToken ) werden zum Testen von Data Builder-Klassen "versteckt". Dadurch werden unsere Tests besser lesbar und einfacher zu warten.

    Nachteile:

    • In diesem Tutorial wird nicht beschrieben, wie wir Integrationstests für die Anmeldung in sozialen Netzwerken schreiben können (Anmeldung über einen Anbieter für soziale Anmeldungen). Ich habe versucht, einen Weg zu finden, diese Tests zu schreiben, ohne einen externen Anmeldeanbieter zu verwenden, aber mir ist einfach die Zeit ausgegangen (es schien kompliziert und ich wollte diesen Blogbeitrag veröffentlichen).

    Dieser Blogbeitrag beendet mein Tutorial „Hinzufügen von Social Sign-in zu einer Spring MVC-Anwendung“.

    Ich werde ein ähnliches Tutorial schreiben, das beschreibt, wie wir in Zukunft soziale Anmeldungen zu einer von Spring unterstützten REST-API hinzufügen können. In der Zwischenzeit möchten Sie vielleicht die anderen Teile dieses Tutorials lesen.

    P.S. Sie können die Beispielanwendung dieses Blogbeitrags von Github herunterladen.


Java-Tag