web-dev-qa-db-fra.com

Spring MockRestServiceServer gérant plusieurs requêtes vers le même URI (détection automatique)

Supposons que j'écris des tests d'intégration Spring pour un service REST A. Ce service, à son tour, rencontre un autre service REST B et obtient une liste d'URI à utiliser) REST service C. C'est une sorte de modèle de découverte automatique. Je veux me moquer des réponses B et C en utilisant MockRestServiceServer.
Maintenant, la réponse de B est une liste d'URI, ils sont tous très similaires, et pour les besoins de l'exemple, disons que ma réponse de B est la suivante:

{
    uris: ["/stuff/1.json", "/stuff/2.json", "/stuff/39.json", "/stuff/47.json"]
}

Simplement, le service A ajoutera chacun d'eux à l'URL de base du service C et fera ces demandes.
Se moquer de B est facile puisqu'il ne s'agit que d'une seule demande.
Se moquer de C est un problème car je devrais me moquer de chaque URI pour obtenir une réponse fictive appropriée. Je veux l'automatiser!
.

public class RequestContainsUriMatcher implements RequestMatcher {
    private final String uri;

    public RequestContainsUriMatcher(String uri){
        this.uri = uri;
    }

    @Override
    public void match(ClientHttpRequest clientHttpRequest) throws IOException, AssertionError {
        assertTrue(clientHttpRequest.getURI().contains(uri));
    }
}

Cela fonctionne bien car maintenant je peux le faire:

public RequestMatcher requestContainsUri(String uri) {
    return new RequestContainsUriMatcher(uri);
}

MockRestServiceServer.createServer(restTemplate)
            .expect(requestContainsUri("/stuff"))
            .andExpect(method(HttpMethod.GET))
            .andRespond(/* I will get to response creator */);

Maintenant, tout ce dont j'ai besoin est un créateur de réponse qui connaît l'URL complète de la demande et où se trouvent les données factices (je les aurai sous forme de fichiers json dans le dossier des ressources de test):

public class AutoDiscoveryCannedDataResponseCreator implements ResponseCreator {
    private final Function<String, String> cannedDataBuilder;

    public AutoDiscoveryCannedDataResponseCreator(Function<String, String> cannedDataBuilder) {
        this.cannedDataBuilder = cannedDataBuilder;
    }

    @Override
    public ClientHttpResponse createResponse(ClientHttpRequest clientHttpRequest) throws IOException {
        return withSuccess(cannedDataBuilder.apply(requestUri), MediaType.APPLICATION_JSON)
                    .createResponse(clientHttpRequest);
    }
}

Maintenant, les choses sont faciles, je dois écrire un générateur qui prend l'URI de demande comme une chaîne et renvoie des données factices, comme une chaîne! Brillant!

public ResponseCreator withAutoDetectedCannedData() {
    Function<String, String> cannedDataBuilder = new Function<String, String>() {
        @Override
        public String apply(String requestUri) {
            //logic to get the canned data based on URI
            return cannedData;
        }
    };

    return new AutoDiscoveryCannedDataResponseCreator(cannedDataBuilder);
}

MockRestServiceServer.createServer(restTemplate)
            .expect(requestContainsUri("/stuff"))
            .andExpect(method(HttpMethod.GET))
            .andRespond(withAutoDetectedCannedData());

Ça fonctionne bien! .... Pour la première demande.
Après la première demande (/stuff/1.json), mon MockRestServiceServer répond avec le message "Erreur d'assertion: aucune autre demande n'est attendue".
Fondamentalement, je peux faire autant de demandes à ce MockRestServiceServer qu'il y a eu d'appels .expect () dessus. Et comme je n'en avais qu'un seul, seule la première demande sera traitée.
Y a-t-il un moyen de contourner cela? Je ne veux vraiment pas me moquer du service C 10 ou 20 fois ...

16
Daniel Gruszczyk

Si vous regardez la classe MockRestServiceServer, elle prend en charge deux méthodes 'expect ()'. La première par défaut est 'ExpectedCount.once ()' mais la seconde méthode vous permet de changer cette valeur

public ResponseActions expect(RequestMatcher matcher) {
    return this.expect(ExpectedCount.once(), matcher);
}

public ResponseActions expect(ExpectedCount count, RequestMatcher matcher) {
    return this.expectationManager.expectRequest(count, matcher);
}

J'ai trouvé ce ticket MockRestServiceServer devrait permettre à une attente de se produire plusieurs fois qui décrit certaines options pour la deuxième méthode.

Dans votre cas, je pense que l'ajout d'une importation statique et l'utilisation de la méthode manyTimes () est un code plus net que la boucle for

MockRestServiceServer
            .expect(manyTimes(), requestContainsUri("/stuff"))
            .andExpect(method(HttpMethod.GET))

D'autres options sont

once();
manyTimes();
times(5);
min(2);
max(8);
between(3,6);
23
emeraldjava

EDIT: Voir la réponse de @emeraldjava qui montre la bonne solution pour les utilisateurs de Spring 4.3+.

Malheureusement, il n'existe aucun mécanisme Nice pour attendre plusieurs appels. Soit vous le faites manuellement, soit vous utilisez des boucles, par exemple:

for (int i = 0; i < 10; i++) {           
        mockRestServiceServer
                .expect(requestContainsUri("/stuff"))
                .andExpect(method(HttpMethod.GET))
                .andRespond(withAutoDetectedCannedData());
}

Sachez que les demandes doivent être appelées sans interruption, par ex. il ne peut pas y avoir d'autre appel REST qui ne correspond pas à l'URI "/ stuff".

10
rapasoft