DEV Community

Igor Rudel
Igor Rudel

Posted on

Dica Java: Métodos estáticos não! #003

Essa dica: é muito importante para quem faz testes unitários!

Imagine uma service com 2 métodos que possuem a mesma validação (IF) como abaixo:

@Service
@RequiredArgsConstructor
public class PersonService {

    private static final int ADULT_AGE = 18;

    public void createAdult(final PersonDomain person) {
        if (ADULT_AGE > Period.between(person.birthdate(), LocalDate.now()).getYears()) {
            throw new UnsupportedOperationException("person.is.not.adult");
        }
    }

    public void registerCNH(final PersonDomain person) {
        if (ADULT_AGE > Period.between(person.birthdate(), LocalDate.now()).getYears()) {
            throw new UnsupportedOperationException("person.is.not.adult");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Há uma replicação de código: IF + exceção.

Se o projeto tivesse testes unitários, para ter a cobertura teria que ser algo como o código abaixo:

@ExtendWith(MockitoExtension.class)
class PersonServiceTest {

    @InjectMocks
    private PersonService service;

    @Nested
    class WhenCreateAdult {

        @Test
        void shouldDoesNotThrow() {
            final var person = new PersonDomain(LocalDate.now().minusYears(18));

            assertDoesNotThrow(() -> service.createAdult(person));
        }

        @Test
        void shouldDoesNotThrow2() {
            final var person = new PersonDomain(LocalDate.now().minusYears(19));

            assertDoesNotThrow(() -> service.createAdult(person));
        }

        @Test
        void shouldThrow() {
            final var person = new PersonDomain(LocalDate.now().minusYears(17));

            assertThatThrownBy(() -> service.createAdult(person))
                .isInstanceOf(UnsupportedOperationException.class)
                .hasMessage("person.is.not.adult");
        }
    }

    @Nested
    class WhenRegisterCNH {

        @Test
        void shouldDoesNotThrow() {
            final var person = new PersonDomain(LocalDate.now().minusYears(18));

            assertDoesNotThrow(() -> service.registerCNH(person));
        }

        @Test
        void shouldDoesNotThrow2() {
            final var person = new PersonDomain(LocalDate.now().minusYears(19));

            assertDoesNotThrow(() -> service.registerCNH(person));
        }

        @Test
        void shouldThrow() {
            final var person = new PersonDomain(LocalDate.now().minusYears(17));

            assertThatThrownBy(() -> service.registerCNH(person))
                .isInstanceOf(UnsupportedOperationException.class)
                .hasMessage("person.is.not.adult");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Terá então replicação de código e de teste unitário.

É comum para evitar a replicação de código, separar em outra classe e reaproveitar o mesmo código.

public class PersonValidator {

    private static final int ADULT_AGE = 18;

    public static void verifyAdult(final PersonDomain person) {
        if (ADULT_AGE > Period.between(person.birthdate(), LocalDate.now()).getYears()) {
            throw new UnsupportedOperationException("person.is.not.adult");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Na service o código ficaria.

@Service
@RequiredArgsConstructor
public class PersonService {

    public void createAdult(final PersonDomain person) {
        PersonValidator.verifyAdult(person);
    }

    public void registerCNH(final PersonDomain person) {
        PersonValidator.verifyAdult(person);
    }
}
Enter fullscreen mode Exit fullscreen mode

OK, foi resolvido a replicação do IF e do throw, porém o teste unitário ainda está replicado! E por isso é recomendado sempre usar Bean's! Evitar o uso de métodos estáticos e transformar a classe em uma Bean.

@Component
public class PersonValidator {

    private static final int ADULT_AGE = 18;

    public void verifyAdult(final PersonDomain person) {
        if (ADULT_AGE > Period.between(person.birthdate(), LocalDate.now()).getYears()) {
            throw new UnsupportedOperationException("person.is.not.adult");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

A PersonService com a injeção de dependência da nova Bean.

@Service
@RequiredArgsConstructor
public class PersonService {

    private final PersonValidator validator;

    public void createAdult(final PersonDomain person) {
        validator.verifyAdult(person);
    }

    public void registerCNH(final PersonDomain person) {
        validator.verifyAdult(person);
    }
}
Enter fullscreen mode Exit fullscreen mode

O teste unitário fica único.

@ExtendWith(MockitoExtension.class)
class PersonValidatorTest {

    @InjectMocks
    private PersonValidator validator;

    @Nested
    class WhenVerifyAdult {

        @Test
        void shouldDoesNotThrow() {
            final var person = new PersonDomain(LocalDate.now().minusYears(18));

            assertDoesNotThrow(() -> validator.verifyAdult(person));
        }

        @Test
        void shouldDoesNotThrow2() {
            final var person = new PersonDomain(LocalDate.now().minusYears(19));

            assertDoesNotThrow(() -> validator.verifyAdult(person));
        }

        @Test
        void shouldThrow() {
            final var person = new PersonDomain(LocalDate.now().minusYears(17));

            assertThatThrownBy(() -> validator.verifyAdult(person))
                .isInstanceOf(UnsupportedOperationException.class)
                .hasMessage("person.is.not.adult");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

E o teste unitário na PersonService torna-se apenas um verify.

@ExtendWith(MockitoExtension.class)
class PersonServiceTest {

    @InjectMocks
    private PersonService service;

    @Mock
    private PersonValidator validator;

    @Nested
    class WhenCreateAdult {

        @Test
        void shouldDoesNotThrow() {
            final var person = new PersonDomain(LocalDate.now());

            assertDoesNotThrow(() -> service.createAdult(person));

            verify(validator).verifyAdult(person);
        }
    }

    @Nested
    class WhenRegisterCNH {

        @Test
        void shouldDoesNotThrow() {
            final var person = new PersonDomain(LocalDate.now());

            assertDoesNotThrow(() -> service.registerCNH(person));

            verify(validator).verifyAdult(person);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Obtém-se o mesmo resultado com o mesmo objetivo e ainda mantém boas práticas de código e testes.

No exemplo foi utilizado apenas um método com retorno void e utilizado apenas em dois locais do sistema, porém esse exemplo se aplica a cenários mais complexos onde condicionais ou estados de objetos influenciam e alteram os comportamentos dos componentes do sistema em N locais. Essa dica torna mais fácil, mais eficiente e menos desgastantes os testes unitários.

Quadratic AI

Quadratic AI – The Spreadsheet with AI, Code, and Connections

  • AI-Powered Insights: Ask questions in plain English and get instant visualizations
  • Multi-Language Support: Seamlessly switch between Python, SQL, and JavaScript in one workspace
  • Zero Setup Required: Connect to databases or drag-and-drop files straight from your browser
  • Live Collaboration: Work together in real-time, no matter where your team is located
  • Beyond Formulas: Tackle complex analysis that traditional spreadsheets can't handle

Get started for free.

Watch The Demo 📊✨

Top comments (0)