Erzeugung von Mocks mit dem Builder-Pattern

05.10.2022

Die Erzeugung von Mocks mit dem Builder-Pattern erlaubt es, Tests lesbarer zu gestalten, Wiederverwendung zu fördern und sich im Unit-Test auf das Wesentliche zu konzentrieren.

In einem Vorgängerartikel habe ich bereits ausführlich die Vorteile des Builder-Patterns für die Erzeugung von Testobjekten dargelegt. Auf dieselbe Art und Weise lassen sich aber natürlich auch Mocks erzeugen.

In unserem Fall ein Authentifizierungstoken, das für die Tests simuliert werden soll:

...
import static java.util.stream.Collectors.toList;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.security.Principal;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.mockito.Mockito;

import de.example.RoleEnum;


public class MockTokenBuilder {

    private Set<String> roles = new HashSet<>();
	
    private String username;

    public static MockTokenBuilder builder() {
        return new MockTokenBuilder();
    }

    public MockTokenBuilder withRoles(final Set roles) {
        this.roles = roles;
        return this;
    }

    public MockTokenBuilder withUsername(final String username) {
        this.username = username;
        return this;
    }

    public MockTokenBuilder withAdminMasterUsername() {
        this.username = "admin_master";
        return this;
    }
	
    public MockTokenBuilder withMustermann() {
        this.username = "m.mustermann";
        initRolesIfNull();
		this.roles.add(RoleEnum.NORMAL_USER.getRoleName());
        return this;
    }	

    public KeycloakAuthenticationToken build() {
        final KeycloakAuthenticationToken tokenMock = 
             mock(KeycloakAuthenticationToken.class, Mockito.RETURNS_DEEP_STUBS);
        return build(tokenMock);
    }

    public KeycloakAuthenticationToken build(final KeycloakAuthenticationToken tokenMock) {
        if (roles != null) {
            // Dieser Aufruf von Mockito.when() auf der Methodenkette 
            // wird durch Mockito.RETURNS_DEEP_STUBS weiter oben möglich
            // sonst müssten wir den Account-Mock hier manuell erstellen 
            // und den Token-Mock anweisen diesen bei getAccount() zurückzugeben.
            when(tokenMock.getAccount().getRoles()).thenReturn(roles);
        }

        if (username != null) {
            when(tokenMock.getPrincipal().getName()).thenReturn(this.username);
        }
        return tokenMock;
    }

    public MockTokenBuilder withRoles(final List roles) {
        if (roles == null) {
           this.roles = roles;
        } else {
           this.roles.addAll(roles);
        }
        return this;
    }

    public MockTokenBuilder withRole(final String role) {
        this.roles.add(role);
        return this;
    }

    public MockTokenBuilder withAllAdminRoles() {
        this.roles.addAll(RoleEnum.getAdminRoles().stream().map(RoleEnum::getRoleName).collect(toList()));
        return this;
    }
}

In Tests können mit dem Builder dann ganz einfach verschiedene Berechtigungskonstellationen getestet werden.

Beispiele:

	final KeycloakAuthenticationToken tokenMock = MockTokenBuilder.builder()
			.withMustermann()
			.withRoles(Set.of(RoleEnum.TESTER.getDisplayName()))
			.build();
	final KeycloakAuthenticationToken tokenMock = MockTokenBuilder.builder()
			.withUsername("an_admin_with_all_admin_roles")
			.withAllAdminRoles()
			.build();

Fazit: Durch Mocks mit dem Builder-Pattern werden Unit-Tests besser lesbar

Die Erzeugung von Mocks mit dem Builder-Pattern verbessert die Lesbarkeit von Unit-Tests. So kann man sich im Test auf die eigentlich zu testende Logik fokussieren und wird nicht von wiederkehrenden Mock-Initialisierungen abgelenkt. Leider sehe ich oft, dass Mocks in Setup-Methoden initialisiert werden und in den einzelnen Tests dann mit Mockito.when um spezielles Verhalten erweitert werden. Dieses Vorgehen sollte man vermeiden, da man so für das Verständnis des Tests zwischen Setup-Methode und Test hin und her springen muss. Dagegen macht das Builder-Pattern bei Verwendung von sprechenden Methodennamen sehr klar, auf was für konkreten Mocks die Tests jeweils aufbauen. Durch die Verwendung des Builder-Patterns für die Initialisierung von Testobjekten und Mocks kann man Unit-Tests so lesbar gestalten, dass ihr Inhalt auf einen Blick erfasst werden kann und zum Verständnis kaum Programmierkenntnisse erforderlich  sind.

 

Links:

 

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*