Quero um repositório (digamos UserRepository
) criado com a ajuda do Spring Data. Eu sou novo no Spring-Data (mas não no Spring) e uso este tutorial . Minha escolha de tecnologias para lidar com o banco de dados é JPA 2.1 e Hibernate. O problema é que não tenho noção de como escrever testes de unidade para esse repositório.
Vamos usar o create()
método, por exemplo. Enquanto estou trabalhando no teste primeiro, devo escrever um teste de unidade para ele - e é aí que me deparei com três problemas:
Primeiro, como injetar uma simulação de uma
EntityManager
implementação inexistente de umaUserRepository
interface? O Spring Data geraria uma implementação baseada nesta interface:public interface UserRepository extends CrudRepository<User, Long> {}
No entanto, não sei como forçá-lo a usar uma
EntityManager
simulação e outras simulações - se eu tivesse escrito a implementação, provavelmente teria um método setterEntityManager
, permitindo que eu usasse a simulação para o teste de unidade. (Quanto à conectividade banco de dados real, eu tenho umaJpaConfiguration
classe, anotado com@Configuration
e@EnableJpaRepositories
, que define programaticamente feijão paraDataSource
,EntityManagerFactory
,EntityManager
etc. - mas repositórios deve ser amigável-teste e permitem substituir essas coisas).Segundo, devo testar interações? É difícil para mim descobrir quais métodos
EntityManager
eQuery
como devem ser chamados (semelhante a issoverify(entityManager).createNamedQuery(anyString()).getResultList();
), pois não sou eu quem está escrevendo a implementação.Terceiro, devo testar os métodos gerados pelos dados da Spring em primeiro lugar? Como eu sei, o código da biblioteca de terceiros não deve ser testado em unidade - apenas o código que os desenvolvedores escrevem é testado em unidade. Mas se isso é verdade, ele ainda traz a primeira volta pergunta para a cena: dizer, eu tenho um par de métodos personalizados para meu repositório, para o qual eu estarei escrevendo implementação, como faço para injetar meus simulações de
EntityManager
eQuery
para a final, gerados repositório?
Nota: Eu estarei de test-drive meus repositórios usando tanto a integração e os testes de unidade. Para meus testes de integração, estou usando um banco de dados HSQL na memória e obviamente não estou usando um banco de dados para testes de unidade.
E provavelmente a quarta pergunta, é correto testar a criação e a recuperação corretas de gráfico de objeto nos testes de integração (por exemplo, eu tenho um gráfico de objeto complexo definido com o Hibernate)?
Atualização: hoje continuo experimentando a injeção simulada - criei uma classe interna estática para permitir a injeção simulada.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional
@TransactionConfiguration(defaultRollback = true)
public class UserRepositoryTest {
@Configuration
@EnableJpaRepositories(basePackages = "com.anything.repository")
static class TestConfiguration {
@Bean
public EntityManagerFactory entityManagerFactory() {
return mock(EntityManagerFactory.class);
}
@Bean
public EntityManager entityManager() {
EntityManager entityManagerMock = mock(EntityManager.class);
//when(entityManagerMock.getMetamodel()).thenReturn(mock(Metamodel.class));
when(entityManagerMock.getMetamodel()).thenReturn(mock(MetamodelImpl.class));
return entityManagerMock;
}
@Bean
public PlatformTransactionManager transactionManager() {
return mock(JpaTransactionManager.class);
}
}
@Autowired
private UserRepository userRepository;
@Autowired
private EntityManager entityManager;
@Test
public void shouldSaveUser() {
User user = new UserBuilder().build();
userRepository.save(user);
verify(entityManager.createNamedQuery(anyString()).executeUpdate());
}
}
No entanto, a execução desse teste fornece o seguinte rastreamento de pilha:
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContext(CacheAwareContextLoaderDelegate.java:99)
at org.springframework.test.context.DefaultTestContext.getApplicationContext(DefaultTestContext.java:101)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:319)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:212)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:232)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:175)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:77)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepository': Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:
PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'entityManager' threw exception; nested exception is java.lang.IllegalArgumentException: JPA Metamodel must not be null!
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1493)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1197)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:684)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:121)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:100)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:250)
at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContextInternal(CacheAwareContextLoaderDelegate.java:64)
at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContext(CacheAwareContextLoaderDelegate.java:91)
... 28 more
Caused by: org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:
PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'entityManager' threw exception; nested exception is java.lang.IllegalArgumentException: JPA Metamodel must not be null!
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:108)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:62)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1489)
... 44 more
fonte
pom.xml
.Com o Spring Boot + Spring Data, tornou-se bastante fácil:
A solução da @heez traz o contexto completo, apenas o necessário para que o JPA + Transaction funcione. Observe que a solução acima exibirá um banco de dados de teste na memória, pois esse pode ser encontrado no caminho de classe.
fonte
@RunWith(SpringRuner.class)
já está incluído no@DataJpaTest
.@RunWith(SpringRunner.class
inicia o contexto da mola, o que significa que está verificando a integração entre várias unidades. O teste de unidade está testando uma única unidade -> classe única. Então você escreveMyClass sut = new MyClass();
e testa o objeto sut (sut = serviço em teste)Isso pode ser um pouco tarde demais, mas escrevi algo para esse fim. Minha biblioteca zombará dos métodos básicos do repositório crud para você e interpretará a maioria das funcionalidades dos seus métodos de consulta. Você precisará injetar funcionalidades para suas próprias consultas nativas, mas o resto é feito para você.
Dê uma olhada:
https://github.com/mmnaseri/spring-data-mock
ATUALIZAR
Isso agora está no Maven central e em muito boa forma.
fonte
Se você estiver usando o Spring Boot, pode simplesmente usar
@SpringBootTest
para carregar o seuApplicationContext
(que é o que o seu stacktrace está latindo para você). Isso permite que você conecte automaticamente seus repositórios de dados de primavera. Certifique-se de adicionar@RunWith(SpringRunner.class)
para que as anotações específicas da mola sejam selecionadas:Você pode ler mais sobre os testes na inicialização por mola nos documentos deles .
fonte
Predicate
s (que foi o meu caso de uso) funciona muito bem.Na última versão do boot inicial 2.1.1.RELEASE , é simples como:
Código completo:
https://github.com/jrichardsz/spring-boot-templates/blob/master/003-hql-database-with-integration-test/src/test/java/test/CustomerRepositoryIntegrationTest.java
fonte
2.0.0.RELEASE
Spring Boot.Quando você realmente deseja escrever um i-test para um repositório de dados do Spring, pode fazê-lo assim:
Para seguir este exemplo, você precisa usar estas dependências:
fonte
Eu resolvi isso usando desta maneira -
fonte
Com o JUnit5 e o
@DataJpaTest
teste será parecido com (código kotlin):Você pode usar
TestEntityManager
doorg.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
pacote para validar o estado da entidade.fonte