Jak zaimplementować TransactionTemplate w testach in-memory

0

Cześć,
Testuję walidację wniosku rejestracji. DAO to interfejs, stworzyłem klasę która go implementuje i dodałem w niej mechanizm przechowywania danych w pamięci na HashMapie.

Przykład testu:
Jeśli username z requestu istnieje w systemie, sprawdzam czy rzuci wyjątek.

Mam taką klase która tworzy obiekty User do testów - UserBuilder.

public class UserBuilder {

    private final RoleDAO roleDAO;
    private final PasswordEncoder encoder;

    private Integer id = 20;
    private String username = "Username";
    private String email = "email@mail.mail";
    private String password = "Password#3";
    private Set<Role> roles = new HashSet<>();

    public UserBuilder(RoleDAO roleDAO, PasswordEncoder encoder) {
        this.roleDAO = roleDAO;
        this.encoder = encoder;
    }

    public UserBuilder copy() {
        UserBuilder copy = new UserBuilder(this.roleDAO, this.encoder);
        copy.id = this.id;
        copy.username = this.username;
        copy.email = this.email;
        copy.password = this.password;
        copy.roles = new HashSet<>(this.roles);
        return copy;
    }

    public UserBuilder withId(Integer id) {
        this.id = id;
        return this;
    }

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

    public UserBuilder withEmail(String email) {
        this.email = email;
        return this;
    }

    public UserBuilder withPassword(String password) {
        this.password = password;
        return this;
    }

    public UserBuilder withRole(UserRole roleName) {
        Role role = roleDAO.findByName(roleName).orElseThrow(
                () -> new RoleNotFoundException("Role not found " + roleName));
        this.roles.add(role);
        return this;
    }

    public User build() {
        Role defaultRole = roleDAO.findByName(UserRole.ROLE_USER).orElseThrow(
                () -> new RoleNotFoundException("Role not found " + UserRole.ROLE_USER));

        if (!roles.contains(defaultRole)) {
            this.roles.add(defaultRole);
        }

        User user = new User();
        user.setId(this.id);
        user.setUsername(this.username);
        user.setEmail(this.email);
        user.setPassword(this.encoder.encode(this.password));
        user.setRoles(this.roles);

        return user;
    }
}

UserFixture - klasa pomocnicza

public class UserFixture {

    private final UserBuilder userBuilder;
    private final TransactionTemplate transactionTemplate;
    private final UserDAO userDAO;

    public UserFixture(UserBuilder userBuilder,
                       TransactionTemplate transactionTemplate,
                       UserDAO userDAO) {
        this.userBuilder = userBuilder;
        this.transactionTemplate = transactionTemplate;
        this.userDAO = userDAO;
    }

    public void createUserWithId(Integer id) {
        createUser(builder -> builder.withId(id));
    }

    public void createUserWithUsername(String username) {
        createUser(builder -> builder.withUsername(username));
    }

    public void createUserWithEmail(String email) {
        createUser(builder -> builder.withEmail(email));
    }
    public void createRegularUser() {
        createUser(builder -> builder.withRole(UserRole.ROLE_USER));
    }

    public void createAdminUser() {
        createUser(builder -> builder.withRole(UserRole.ROLE_ADMIN));
    }
    public void createUserWithUsernameAndPassword(String username, String password) {
        createUser(builder -> builder.withUsername(username).withPassword(password));
    }

    private void createUser(Consumer<UserBuilder> config) {
        UserBuilder builderCopy = userBuilder.copy();
        config.accept(builderCopy);
        User user = builderCopy.build();
        saveUserAndAssignRoles(user);
    }
    private void saveUserAndAssignRoles(User user) {
        transactionTemplate.executeWithoutResult(status -> {
            int userId = userDAO.save(user);
            user.getRoles().forEach(role -> userDAO.assignRoleToUser(userId, role.getId()));
        });
    }
}

Jak widzicie w UserFixture mam zależność TransactionTemplate.

Przejdźmy do klasy testowej.

class RequestValidationTest {

    private RegistrationRequests requests;
    private InMemoryUserDAO userDAO;
    private InMemoryRoleDAO roleDAO;
    private UserFixture userFixture;
    private RequestValidation validation;

    @BeforeEach
    void setUp() {
        roleDAO = new InMemoryRoleDAO();
        userDAO = new InMemoryUserDAO(roleDAO);

        roleDAO.addDefaultRoles();

        validation = new RequestValidation(userDAO);

        PasswordEncoder encoder = new BCryptPasswordEncoder();
        UserBuilder userBuilder = new UserBuilder(roleDAO, encoder);

        TransactionTemplate transactionTemplate = new MockTransactionTemplate(); // klasa mock?
        userFixture = new UserFixture(userBuilder, transactionTemplate, userDAO);

        requests = new RegistrationRequests();
    }

    @AfterEach
    void cleanUp() {
        roleDAO.clear();
        userDAO.clear();
    }

    @Test
    void whenUserWithUsernameExists_ThrowException() {
        createUserWithUsername("Alex");
        assertThrows(CredentialValidationException.class, () -> validate(requests.requestWithUsername("Alex")));

    }

    private void validate(RegisterRequest requests) {
        validation.validate(requests);
    }

    private void createUserWithUsername(String username) {
        userFixture.createUserWithUsername(username);
    }
}

W klasie testowej tworzę instancję UserFixture, która ma zależność TransactionTemplate. W jaki sposób najlepiej utworzyć instancję TransactionTemplate, zważywszy, że używam pamięci wewnętrznej opartej na HashMapie?

Na razie zrobiłem tak, że utworzyłem klasę MockTransactionTemplate, która rozszerza TransactionTemplate, i metody nadpisuję tak aby nic nie robiły. Czy to dobre rozwiązanie?

0

mam problem z tym builderem, a już w ogóle nie rozumiem co robi User w TransactionTemplate. A już w ogóle nie rozumiem UserFixture? Btw, testowałeś UserFixture? I chcesz mieć to wszystko w HashMapie? I rzucać wyjątkami?

0
trojanus__ napisał(a):

mam problem z tym builderem

Co jest z nim nie tak?

trojanus__ napisał(a):

a już w ogóle nie rozumiem co robi User w TransactionTemplate.

User w TransactionTemplate? Nie rozumiem, co masz na myśli?

trojanus__ napisał(a):

A już w ogóle nie rozumiem UserFixture?

Potrzebuję stworzyć użytkownika w wielu miejscach, podczas testowania wielu funkcjonalności. Uznałem, że zamiast tworzyć za każdym razem metodę pomocniczą w klasie testowej do tworzenia użytkownika, może będzie lepiej delegować to do innej klasy. Czy to ma sens? Nie wiem, dopiero się uczę.

1

odpowiadam w kolejności:

  1. ten builder wygląda koślawo. Przypuszczam, że działa. Znam taką konstrukcję, natomiast wolę i polecam buildera Josha Blocha. Albo możesz wykorzystać buildera z Lomboka, i sobie możesz zobaczyć jak wygląda po kompilacji, jest praktycznie 1:1 z Blochem. Ogólnie chodzi o settery z klasy User. Można zrobić klasę User i wewnątrz klasy User zrobić statyczną klasę Builder. Wtedy pozbywasz się setterów i masz wszystko jak Bozia przykazała stworzone przez konstruktory i dodatkowy plus - robisz fluent API. Polecam.
  2. sorry, mój błąd, myślałem o czymś zupełnie innym 🙂
  3. znowu mój błąd, bo zastanawiałem się po co tyle voidów, a to faktycznie nic nie zwraca. Jako klasa pomocnicza wygląda OK, dobre podejście.
1
Ornstein napisał(a):

Na razie zrobiłem tak, że utworzyłem klasę MockTransactionTemplate, która rozszerza TransactionTemplate, i metody nadpisuję tak aby nic nie robiły. Czy to dobre rozwiązanie?

Jeśli pozwala Ci przetestować to co chcesz przetestować, nie przeszkadza Ci w niczym (np. nie dodaje ciasnego powiązania) i dobrze Ci się z tego korzysta - to tak, jest to w miarę dobre.

Aczkolwiek muszę przyznać że RequestValidationTest ma bardzo rozbudowany setup, jak na test który ma sprawdzać to czy nie da się stworzyć dwóch userów o takim samym nicku. Delikatnie to wygląda jak test napisany po fakcie.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.