Test a constructor with more than one parameter

Asked

Viewed 1,748 times

1

I am trying to create a test class for my constructor, but it receives three parameters and these parameters are validated within the class itself, to allow or not the creation of its instance.

Follows code:

public class Mamiferos{
private String nome;
private Date dataNasc;
private String sexo;

public Mamiferos(String nome, Date dataNasc, String sexo){      
    try {

        validaSexo();
        validaNome();
        validaData();

        }catch (Exception e) {
        // TODO: handle exception

        }finally{
            this.nome = nome;
            SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy");
            try {
                this.dataNasc = format.parse(dataNasc.toString());
            } catch (ParseException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            this.sexo =sexo;            
        }
}

private Boolean validaData() {

    Date dataAtual = new Date(System.currentTimeMillis());
    if (dataNasc.before(dataAtual)){
        return true;
    }else{
        System.out.println("Por favor, digite uma data válida.\n");
        return false;
    }   
}

private Boolean validaNome() {

    if (nome.isEmpty() || nome.length()<5) {
        System.out.println("O nome deve ter ao menos 5 caracteres.\nDigite novamente.");
        return false;           
    }else{
        return true;        
    }       
}

private Boolean validaSexo() {

    if (sexo != "Male" || sexo != "Female" || sexo.isEmpty()) {

        System.out.println("O sexo não pode ser nulo.\nDigite o sexo (Male/Female).\n");
        return false;

    } else {
        return true;
    }       
}

Down with my test class unsuccessful attempt:

public class NameTest {
    @Test
    public void test() {

    }

Yes, empty, I don’t know how to test the validators. I didn’t want to make them public because it doesn’t make sense.

PS: I accept suggestions.

Thank you!

  • Hello, I don’t know this Hibernate Validator. Is it necessary to create a persistence to use it? Thanks.

3 answers

3


First your class has some problems:

  • The methods validaData(), validaNome() and validaSexo() return a Boolean indicating whether the validation was successful or not. However, your constructor simply ignores the returned value, and as a result, it accepts the parameters even if they are wrong, which is equivalent to not performing any validation.
  • The methods validaData(), validaNome() and validaSexo() look at the values of the fields of your class before they have been defined, and therefore always see the values null and will always fail validation even if your class is instantiated correctly.
  • If any of the methods validaData(), validaNome() or validaSexo() make an exception, you would just ignore it, and that means simply ignoring any validation problem and accepting anything.
  • The methods validaData(), validaNome() and validaSexo() never return null and they’re not even using Boolean as the specification of some generic covariant type. For this reason, use boolean instead of Boolean. Or use the exception mechanism appropriately and change the type to void.
  • There is no sense to define the values of the fields in the block finally of the builder. In fact, it doesn’t even make sense to use the finally here.

    If the block try end with one exception, the code that invoked the constructor cannot assign the result of the constructor call to any variable, and therefore the newly created instance will be lost. Now, if the newly created instance will be lost, setting the values of its fields will be an attitude in vain.

    On the other hand, if the block try end normally, the code of the finally will also be executed normally as if it were just after the try-catch.

    Once your block try just eats the exceptions that are thrown into it, perform the finally would be equivalent to only follow the normal flow after the end of the block try-catch, and in this case your block finally is useless.

  • Validation error messages are thrown away on System.out.println and then forgotten forever. You can’t use them after that.

  • I see no reason to use new Date(System.currentTimeMillis()); rather than simply new Date();. I also find it simpler to set the time for midnight using the GregorianCalendar instead of SimpleDateFormat, because there is no need to treat a ParseException.

  • If the constructor actually builds the object, you will need to test if it built the object properly. Therefore, some getters are needed.

  • Are you comparing Strings using !=. This won’t do what you want. Also your validation will always fail because sexo != "Male" || sexo != "Female" will always be true, after all the sex is always different from male or female, because if it is male, then it is different from female (and therefore gives true) and if it is female, then it is different from male (and also gives true).

There is no point in trying to test the validation of a constructor, if at the end of the day he can’t actually do any validation!

Let’s pack up your class. I’ll use IllegalArgumentException to represent a validation error. I will make the validation methods validate a parameter and return it if it is accepted or make an exception if they reject it. I’ll also put the complexity of playing the Date for midnight (to remove the hour) in a separate method. The idea is that the constructor throws an exception if it receives invalid parameters, which means that in this case the instantiation of the object is for all purposes rejected.

public class Mamiferos {
    private String nome;
    private Date dataNasc;
    private String sexo;

    public Mamiferos(String nome, Date dataNasc, String sexo) {
        this.sexo = validaSexo();
        this.nome = validaNome();
        this.dataNasc = meiaNoite(validaData());
    }

    public String getNome() {
        return nome;
    }

    public Date getDataNasc() {
        return dataNasc;
    }

    public String getSexo() {
        return sexo;
    }

    public static Date meiaNoite(Date data) {
        GregorianCalendar gc = new GregorianCalendar();
        gc.setTime(data);
        gc.set(Calendar.HOUR, 0);
        gc.set(Calendar.MINUTE, 0);
        gc.set(Calendar.SECOND, 0);
        gc.set(Calendar.MILLISECOND, 0);
        return gc.getTime();
    }

    private static Date validaData(Date dataNasc) {
        Date dataAtual = new Date(System.currentTimeMillis());
        if (!dataNasc.before(dataAtual)) {
            throw new IllegalArgumentException("Por favor, digite uma data válida.");
        }
        return dataNasc;
    }

    private static String validaNome(String nome) {
        if (nome.isEmpty() || nome.length() < 5) {
            throw new IllegalArgumentException("O nome deve ter ao menos 5 caracteres.\nDigite novamente.");
        }
        return nome;
    }

    private static String validaSexo(String sexo) {
        if (!"Male".equals(sexo) && !"Female".equals(sexo)) {
            throw new IllegalArgumentException("O sexo não pode ser diferente de Male/Female.");
        }
        return sexo;
    }
}

Now let’s test your class:

public class TestMamiferos {

    private Date amanha() {
        GregorianCalendar gc = new GregorianCalendar();
        gc.add(Calendar.DATE, 1);
        return gc.getTime();
    }

    private Date ontem() {
        GregorianCalendar gc = new GregorianCalendar();
        gc.add(Calendar.DATE, -1);
        return gc.getTime();
    }

    @Test
    public void testInstanciaNormalmenteMale() {
        Date d = ontem();
        Mamiferos m = new Mamiferos("Gatinho", d, "Male");
        Assert.assertEquals("Gatinho", m.getNome());
        Assert.assertEquals(Mamiferos.meiaNoite(d), m.getDataNasc());
        Assert.assertEquals("Male", m.getSexo());
    }

    @Test
    public void testInstanciaNormalmenteFemale() {
        Date d = ontem();
        Mamiferos m = new Mamiferos("Gatinha", d, "Female");
        Assert.assertEquals("Gatinha", m.getNome());
        Assert.assertEquals(Mamiferos.meiaNoite(d), m.getDataNasc());
        Assert.assertEquals("Female", m.getSexo());
    }

    @Test
    public void testInstanciaNomeCurto() {
        try {
            new Mamiferos("A", ontem(), "Female");
            Assert.fail();
        } catch (IllegalArgumentException e) {
            Assert.assertEquals("O nome deve ter ao menos 5 caracteres.\nDigite novamente.", e.getMessage());
        }
    }

    @Test
    public void testInstanciaDataFuturo() {
        try {
            new Mamiferos("Gatinha", amanha(), "Female");
            Assert.fail();
        } catch (IllegalArgumentException e) {
            Assert.assertEquals("Por favor, digite uma data válida.", e.getMessage());
        }
    }

    // Outros testes
}

Anyway, this is the test route. In the tests that verify success, the object is instantiated and its state is checked to make sure that the object has been properly initialized by the constructor. In the tests that verify the failures, the Assert.fail() makes the test fail if the constructor nay throw an exception (once it should cast) and the expected exception is captured and then it is checked if it is correct (i.e., that it is not some other different exception).

I recommend testing all cases where an exception can be cast, such as invalid sex (which I left out of the test class, this is for you to do). And by the way, there are two more things I leave for you to solve and apply the corresponding unit tests:

  • If you pass null as parameters, should it reject? Your code just launches a NullPointerException which is immediately swallowed in this case. My code will launch it without swallowing. Change the code to treat this and put a corresponding test for each case where it might occur.

  • You really should wear one enum to represent sex. This would make your life a lot easier.

And finally, I recommend studying exception treatment, you clearly don’t understand how it works.

1

My suggestion and the Hibernate Validator.

Hibernate Validator is an implementation of the specification Bean Validation.

Validation Bean allows you to apply restriction rules in annotation form.

Mamifero class using Hibernate Validator:

public class Mamifero {

@NotEmpty
@Length(min = 5)
private String nome;

@Past(message = "Por favor, digite uma data válida.")
private Calendar dataNasc;

@NotEmpty(message = "O sexo não pode ser nulo.")
@Pattern(regexp = "(Male|Female)", message = "Digite o sexo (Male/Female).")
private String sexo;

public Mamifero(String nome, Calendar dataNasc, String sexo) {
    this.nome = nome;
    this.sexo = sexo;
    this.dataNasc = dataNasc;
  }
}

To validate:

// Como é de costume nas especificações o método Factory
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();

// O validador
Validator validator = validatorFactory.getValidator();

Calendar dataNasc = Calendar.getInstance(); 
dataNasc.set(Calendar.YEAR, 2003);
dataNasc.set(Calendar.DAY_OF_MONTH, 17);
dataNasc.set(Calendar.MONTH, Calendar.JANUARY);

Mamifero mamifero = new Mamifero("Cachorro", dataNasc.getTime(), "Male");

// Valida e retorna uma lista Set com as restrições violadas como objeto ConstraintViolation
Set<ConstraintViolation<Mamifero>> violations = validator.validate(mamifero);

To validate only one object property:

Set<ConstraintViolation<Classe>> violations = validator.validateProperty(objeto, "propriedade");

The test class:

public class MamiferoTest {

private Validator validator;

@Before
public void setUp() {
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    this.validator = factory.getValidator();
}

@Test
public void testeArgumentoNomeSemNada() {
    Calendar dataNasc = Calendar.getInstance();

    dataNasc.set(Calendar.YEAR, 2003);
    dataNasc.set(Calendar.DAY_OF_MONTH, 17);
    dataNasc.set(Calendar.MONTH, Calendar.JANUARY);

    Mamifero mamifero = new Mamifero("", dataNasc, "Male");

    Set<ConstraintViolation<Mamifero>> violations = validator.validate(mamifero);

    for (ConstraintViolation<Mamifero> violation : violations) {
        System.out.println(violation);
    }

    assertEquals(2, violations.size());
}

@Test
public void testeDataInvalida() {
    Calendar dataNasc = Calendar.getInstance();

    dataNasc.set(Calendar.YEAR, 2016);
    dataNasc.set(Calendar.DAY_OF_MONTH, 17);
    dataNasc.set(Calendar.MONTH, Calendar.JANUARY);

    Mamifero mamifero = new Mamifero("Cachorro", dataNasc, "Male");

    Set<ConstraintViolation<Mamifero>> violations = validator.validate(mamifero);

    for (ConstraintViolation<Mamifero> violation : violations) {
        System.out.println(violation);
    }

    assertEquals(1, violations.size());
}

@Test
public void testeSexoIncorreto() {
    Calendar dataNasc = Calendar.getInstance();

    dataNasc.set(Calendar.YEAR, 2003);
    dataNasc.set(Calendar.DAY_OF_MONTH, 17);
    dataNasc.set(Calendar.MONTH, Calendar.JANUARY);

    Mamifero mamifero = new Mamifero("Cachorro", dataNasc, "Masculino");

    Set<ConstraintViolation<Mamifero>> violations = validator.validate(mamifero);

    for (ConstraintViolation<Mamifero> violation : violations) {
        System.out.println(violation);
    }

    assertEquals(1, violations.size());
  }
}

To include in a Maven project add the following dependencies:

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator</artifactId>
   <version>5.1.3.Final</version>
</dependency>

Hibernate Validator requires implementation of Unified Expression Language (EL):

<dependency> 
   <groupId> javax.el </groupId> 
   <artifactId> javax.el-api </artifactId> 
   <version> 2.2.4 </version> 
</dependency> 
<dependency> 
   <groupId> org.glassfish.web </groupId> 
   <artifactId> javax.el </artifactId> 
   <version> 2.2.4 </version> 
</dependency>

Your project is not low jars on website.

-1

You want to test private methods of a class in a Unit Test.

Solution Number 1 (the ideal is solution 2 approach, this is an alternative)

Write your Test as a public static member of the class that has its own private methods.

You will need:

  • turn your validation methods into static methods.

  • create a public class as a Static Inner class

  • Pass values as parameters for these methods

    The advantage of this approach is that it is the simplest and fastest, but not the best. You will have a public member who has no cohesion with your class. And a mix of different Concerns in the same class. The ideal is to have your tests well separated.

Solution Number 2

Use Reflection to execute your private methods in other contexts.

This second solution works for both static and non-static methods.

Reflection will offer you the option to suppress Fields checks and JVM methods.

public void test()throws Exception{
    Class<?> clazz = Mamiferos.class;
    
    //vai precisar de um construtor publico sem argumento, ou acessar
    //seu contrutor privado, mudar acessibilidade dele e então instanciar
    //Ou apenas crie uma nova instancia de Mamifero
    //Mamifero m = new ....
    Object obj = clazz.newInstance();// = m;

    Method method = clazz.getDeclaredMethod("validaSexo");

    method.setAccesible(true);//Aqui está o segredo, ete método suprirá o modificador de acesso

    method.invoke();//Chamada ao seu método validaSexo
}

As the main negative point is the fact that you have to exceed a more verbose code, which the use of Reflection in Java implies. As for performance, only if you work on ultra-low latency applications will this be something to look forward to. JVM has a property called Fast Reflection:

http://www.oracle.com/technetwork/java/whitepaper-135217.html

For more readable and powerful code for reflective operations, consider using the Gren-Rfletea API;

https://github.com/FilipeGMiranda/GreenReflectea

Browser other questions tagged

You are not signed in. Login or sign up in order to post.