TDD + Validação de Entidades

Galera, estou iniciando um projeto com VRaptor e queria aplicar TDD.

Estudei um pouco de ruby, e eu sempre fazia os testes na validação das entidades.

No VRaptor, todos os exemplos que vi o método que realiza a validação é feito dentro da própria controller, dependendo do Validator para adicionar os erros:

@Resource
public class AlgumaController {
private void validate(final MinhaEntidade entidade) {
		this.validator.checking(new Validations() {
			{
				//validações
			}
		});
	}
}

Para eu testar essa validação dentro da controller fica complicado. Pensei então em colocar esse método dentro da própria entidade:

public class MinhaEntidade {
public Validator validate(Validator validator) {
		validator.checking(new Validations() {
			{
                            //validações
			}
		});
		
		return validator;
	}
}

Mas dentro da entidade eu continuo dependendo do Validator para testar. Existe uma melhor forma de eu testar isso?

Valeu

Para você não depender do Validator, você deve usar um Mock. No pacote br.com.caelum.vraptor.util.test há dois mocks para isso: MockValidator e JSR303MockValidator.

Ok. Eu fiz o seguinte código de teste:

public class MinhaEntidadeTest {
	private Validator	validator;
	
	@Before
	public void setUp() {
		this.validator = new MockValidator();
	}
	
	@Test
	public void nameShouldBeRequired() {
		MinhaEntidade entidade = new MinhaEntidade();
		assertFalse(entidade.validate(this.validator));
		//aqui queria ver se deu determinado erro de validação
	}
}

Na onde esta o comentário, como eu faço para verificar se determinada mensagem foi adicionada nos erros? Quero fazer isso porque quero testar a validação de cada atributo da entidade separadamente.
Exemplo: Um teste para ver se o nome foi preenchido, outro para ver se a idade e etc.

se vc for fazer validações dentro da entidade, use o Hibernate Validator (com anotações @NotEmpty, @Email e @AssertTrue por exemplo)

daí no controller vc só precisa chamar validator.validate(entidade) para executar a validação.

[quote=Lucas Cavalcanti]se vc for fazer validações dentro da entidade, use o Hibernate Validator (com anotações @NotEmpty, @Email e @AssertTrue por exemplo)

daí no controller vc só precisa chamar validator.validate(entidade) para executar a validação.
[/quote]
Lucas, o meu problema não é em fazer a validação mas sim em como fazer os testes de unidade dela.

nao qro ter que testar um controller para ver se ele esta validando, isto é teste de integração e não de unidade.

Quero testar cada item da validação individualmente, assim como posso fazer em ruby por exemplo.

Isso é possível?

se a validação for no controller, com validator.add ou validator.checking(…), vc pode usar o MockValidator e dar expect em ValidationException.

se a validação for no modelo (e vc estiver usando Hibernate Validator), não faz tanto sentido testar. Seria testar que a anotação foi colocada no field (que é o que se faz em ruby, embora não existam anotações).

O que vc pode fazer é testar a integração com o Hibernate Validator. Vc pode usar essa classe que não foi lançada ainda (mas vc pode copiá-la para o seu projeto):

Validator validator = new JSR303MockValidator();
...
MinhaEntidade entidadeEmEstadoInvalido = ...
...
validator.validate(entidadeEmEstadoInvalido);

assertTrue(validator.hasErrors());

[quote=Lucas Cavalcanti]se a validação for no controller, com validator.add ou validator.checking(…), vc pode usar o MockValidator e dar expect em ValidationException.

se a validação for no modelo (e vc estiver usando Hibernate Validator), não faz tanto sentido testar. Seria testar que a anotação foi colocada no field (que é o que se faz em ruby, embora não existam anotações).

O que vc pode fazer é testar a integração com o Hibernate Validator. Vc pode usar essa classe que não foi lançada ainda (mas vc pode copiá-la para o seu projeto):

[code]
Validator validator = new JSR303MockValidator();

MinhaEntidade entidadeEmEstadoInvalido = …

validator.validate(entidadeEmEstadoInvalido);

assertTrue(validator.hasErrors());
[/code][/quote]

Lucas,

tenho o seguinte código de entidade:

public class MinhaEntidade {
public Validator validate(Validator validator) {
		validator.checking(new Validations() {
			{
                            //validações
			}
		});
		
		return validator;
	}
}

E o seguinte teste:

public class MinhaEntidadeTest {
	private Validator	validator;
	
	@Before
	public void setUp() {
		this.validator = new MockValidator();
	}
	
	@Test
	public void nameShouldBeRequired() {
		MinhaEntidade entidade = new MinhaEntidade();
		assertFalse(entidade.validate(this.validator));
		//aqui queria ver se deu determinado erro de validação
	}
}

Porém o validator não lança essa ValidationException. Como faço para ele lança-la?
E outra coisa, eu consigo saber qual das validações falhou pela ValidationException?

Valeu pela ajuda.

ele só lança a exception qdo vc chama um dos métodos

validator.onErrorXXX

vc não precisa da exception, vc pode fazer:

entidade.validate(this.validator);

assertTrue(this.validator.hasErrors());

[quote=Lucas Cavalcanti]ele só lança a exception qdo vc chama um dos métodos

validator.onErrorXXX

vc não precisa da exception, vc pode fazer:

[code]
entidade.validate(this.validator);

assertTrue(this.validator.hasErrors());
[/code][/quote]

Lucas,

Eu entendi como usar o hasErrors, é justamente o que eu faço dentro do método validate() da minha entidade.

Só que dentro do meu validate, eu testo diversas situações.

Ex:
- Nome obrigatório
- Idade obrigatória

Se eu validar do jeito que você falou, eu não tenho saber se o erro que ocorreu na validação é referente ao “Nome obrigatório” ou a “Idade obrigatória”. Eu gostaria de saber qual condição fez a validação falhar, desta maneira eu posso testar cada condição da minha validação individualmente.

Ex do que eu gostaria de fazer:

public void nameShouldBeRequired() {
//cria entidade sem nome, executa a validação e verifica se houve erro de validação no nome
}

public void ageShouldBeRequired() {
//cria entidade sem idade, executa a validação e verifica se houve erro de validação na idade 
}

Se eu utilizar a validação do jeito que você expôs, no teste do nome obrigatório, nada me garante que foi a condição do nome que fez a validação falhar.
Você entendeu a minha dúvida?

Obrigado pela ajuda de novo…

vc pode fazer o seguinte:

  • crie um método que cria uma entidade que deveria ser válida

  • apague o nome

  • assertTrue(hasErrors())

  • entidade válida

  • apague o age

  • assertTrue(hasErrors())

e assim por diante

[quote=Lucas Cavalcanti]vc pode fazer o seguinte:

  • crie um método que cria uma entidade que deveria ser válida

  • apague o nome

  • assertTrue(hasErrors())

  • entidade válida

  • apague o age

  • assertTrue(hasErrors())

e assim por diante[/quote]

Sim Lucas, mas ai se por acaso alguma alteração no código fazer uma validação falhar, todas as outras falharão porque ele verifica apenas se houve um erro, e não se o erro foi da condição específica…

daí é só vc mudar o método que constrói o cara válido :wink:

Eu gostaria de ter certeza que o erro que esta ocorrendo é de determinada condição da validação.

Porque caso eu esteja testando se a idade é valida, e ela para de funcionar e por coincidência outra condição faça a validação falhar…o teste vai passar e me dar a falsa impressão de que esta funcionando…

Mas caso não seja possível verificar qual condição originou a falha na validação, vou fazer do jeito que você sugeriu…

Estava estudando um pouco de ruby e acostumei a poder verificar qual condição falhou na validação, pensei q poderia fazer algo semelhante em java com o vraptor…

Obrigado pela ajuda.

vc vai acoplar o teste com a mensagem de erro, mas tudo bem:

entidade.validate(this.validator);

try {
   this.validator.onErrorUse(Results.nothing());
   Assert.fail("Deveria dar erros de validação");
} catch (ValidationException e) {
    List<Message> errors = e.getErrors();
    //verifica se a mensagem que vc quer está dentro de errors
}

vc pode também criar uma subclasse de MockValidator (ou uma cópia da original) e adicionar esse comportamento:

public MeuMockValidator extends MockValidator {
    
    public void assertContainsMessage(String message) {
        for (Message error : this.getErrors()) {
             if (message.equals(error.getMessage()) {
                 return;
             }
        }
        Assert.fail("Não contém a mensagem " + message);
    }
}

posso até colocar isso no VRaptor original, que acha?
ou vc pode colocar isso num fork seu do VRaptor e me mandar um pull request…

ou se vc acha que ficaria melhor de outro jeito, sugestões são muito bem vindas

[quote=Lucas Cavalcanti]vc vai acoplar o teste com a mensagem de erro, mas tudo bem:

entidade.validate(this.validator);

try {
   this.validator.onErrorUse(Results.nothing());
   Assert.fail("Deveria dar erros de validação");
} catch (ValidationException e) {
    List<Message> errors = e.getErrors();
    //verifica se a mensagem que vc quer está dentro de errors
}

vc pode também criar uma subclasse de MockValidator (ou uma cópia da original) e adicionar esse comportamento:

public MeuMockValidator extends MockValidator {
    
    public void assertContainsMessage(String message) {
        for (Message error : this.getErrors()) {
             if (message.equals(error.getMessage()) {
                 return;
             }
        }
        Assert.fail("Não contém a mensagem " + message);
    }
}

posso até colocar isso no VRaptor original, que acha?
ou vc pode colocar isso num fork seu do VRaptor e me mandar um pull request…

ou se vc acha que ficaria melhor de outro jeito, sugestões são muito bem vindas[/quote]

Era exatamente algo assim que eu tinha pensado.
Talvez algo mais parecido com o método that() do Validations, que recebe uma chave do properties e um varargs:

public void assertContainsMessage(String messageKey, Object... messageParameters) {
        //implementação
    }

No ruby, essa verificação de qual atributo não passou na validação é feita desta maneira:

entidade.errors[:atributo].any?

Talvez se pudéssemos criar algo com reflection a ficar mais parecido com isso, assim poderíamos vincular o teste ao nome do atributo e não a mensagem de erro. Será que isso é possível?

vc pode mudar totalmente o mockValidator e no método add(Message message) guardar um Map<String, Message>, assim o assertContainsMessage ficaria:

messages.containsKey(messageKey);

e a key pode ser o category ao invés da message

Olá Lucas,

Fiz a implementação baseada no que tenho aqui, ficou assim:

package br.com.meuProjeto.vraptor.util.test.validator;

import junit.framework.Assert;
import br.com.caelum.vraptor.validator.I18nMessage;
import br.com.caelum.vraptor.validator.Message;
import br.com.meuProjeto.util.TestUtil;

/**
 * Custom implementation of <b>VRaptor MockValidator</b>
 * 
 * @author Nykolas Lima
 * @since 02/03/2011
 */
public class MockValidator extends br.com.caelum.vraptor.util.test.MockValidator {
	/**
	 * Assert if validator contains the specified message
	 * 
	 * @param messageKey
	 *            i18n key for the specified message
	 * @param messageParameters
	 *            parameters for the i18n message
	 */
	public void assertContainsMessage(String messageKey, Object... messageParameters) {
		I18nMessage expectedMessage = new I18nMessage("validation", messageKey, messageParameters);
		expectedMessage.setBundle(TestUtil.bundle);
		for(Message error : this.getErrors()) {
			if(expectedMessage.getMessage().equals(error.getMessage())) {
				return;
			}
		}
		Assert.fail("Does not contains the message: " + expectedMessage.getMessage());
	}
}

Você acha que vale a pena fazer um fork no VRaptor?

vale a pena sim!

e vc pode usar a classe EmptyBundle como bundle =)

Valeu

Ficou muito bom, Nykolas.