DEV Community

Igor Rudel
Igor Rudel

Posted on

3

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.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

The best way to debug slow web pages cover image

The best way to debug slow web pages

Tools like Page Speed Insights and Google Lighthouse are great for providing advice for front end performance issues. But what these tools can’t do, is evaluate performance across your entire stack of distributed services and applications.

Watch video

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay