18

I have a Spring application and in it I do not use xml configuration, only Java Config. Everything is OK, but when I try to test it I faced problems with enabling autowiring of components in the tests. So let's begin. I have an interface:

@Repository
public interface ArticleRepository extends CrudRepository<Page, Long> {
    Article findByLink(String name);
    void delete(Page page);
}

And a component/service:

@Service
public class ArticleServiceImpl implements ArticleService {
    @Autowired
    private ArticleRepository articleRepository;
...
}

I don't want to use xml configurations so for my tests I try to test ArticleServiceImpl using only Java Configuration. So for the test purpose I made:

@Configuration
@ComponentScan(basePackages = {"com.example.core", "com.example.repository"})
public class PagesTestConfiguration {


@Bean
public ArticleRepository articleRepository() {
       // (1) What to return ?
}

@Bean
public ArticleServiceImpl articleServiceImpl() {
    ArticleServiceImpl articleServiceImpl = new ArticleServiceImpl();
    articleServiceImpl.setArticleRepository(articleRepository());
    return articleServiceImpl;
}

}

In articleServiceImpl() I need to put instance of articleRepository() but it is an interface. How to create new object with new keyword? Is it possible without creating xml configuration class and enable Autowiring? Can autowired be enabled when using only JavaConfigurations during testing?

moffeltje
  • 4,521
  • 4
  • 33
  • 57
Xelian
  • 16,680
  • 25
  • 99
  • 152
  • No you don't. You have `@Autowired` so you don't need to set it. You need to put `@EnableJpaRepositories` on your configuration class to let Spring Data JPA create the beans for you. – M. Deinum Jan 09 '15 at 08:52
  • For ArticleServiceImpl I also have Awtowire so I do not need to write articleServiceImpl() also? Am I right? I can't understand how Spring knows to turn on Autowiring for tests. Error creating bean with name 'articleServiceImpl ': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.musala.repository.ArticleRepository – Xelian Jan 09 '15 at 09:23
  • @M.Deinum has the correct answer.. – Jaiwo99 Jan 09 '15 at 09:40
  • For a unit test, don't use a real repository at all. Refactor your service to use constructor injection and inject a mock repository instead. This will make your tests more isolated and much faster. – chrylis -cautiouslyoptimistic- Jan 09 '15 at 09:59

4 Answers4

13

This is what I have found is the minimal setup for a spring controller test which needs an autowired JPA repository configuration (using spring-boot 1.2 with embedded spring 4.1.4.RELEASE, DbUnit 2.4.8).

The test runs against a embedded HSQL DB which is auto-populated by an xml data file on test start.

The test class:

@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( classes = { TestController.class,
                                   RepoFactory4Test.class } )
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class,
                           DirtiesContextTestExecutionListener.class,
                           TransactionDbUnitTestExecutionListener.class } )
@DatabaseSetup( "classpath:FillTestData.xml" )
@DatabaseTearDown( "classpath:DbClean.xml" )
public class ControllerWithRepositoryTest
{
    @Autowired
    private TestController myClassUnderTest;

    @Test
    public void test()
    {
        Iterable<EUser> list = myClassUnderTest.findAll();

        if ( list == null || !list.iterator().hasNext() )
        {
            Assert.fail( "No users found" );
        }
        else
        {
            for ( EUser eUser : list )
            {
                System.out.println( "Found user: " + eUser );
            }
        }
    }

    @Component
    static class TestController
    {
        @Autowired
        private UserRepository myUserRepo;

        /**
         * @return
         */
        public Iterable<EUser> findAll()
        {
            return myUserRepo.findAll();
        }
    }
}

Notes:

  • @ContextConfiguration annotation which only includes the embedded TestController and the JPA configuration class RepoFactory4Test.

  • The @TestExecutionListeners annotation is needed in order that the subsequent annotations @DatabaseSetup and @DatabaseTearDown have effect

The referenced configuration class:

@Configuration
@EnableJpaRepositories( basePackageClasses = UserRepository.class )
public class RepoFactory4Test
{
    @Bean
    public DataSource dataSource()
    {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        return builder.setType( EmbeddedDatabaseType.HSQL ).build();
    }

    @Bean
    public EntityManagerFactory entityManagerFactory()
    {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl( true );

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter( vendorAdapter );
        factory.setPackagesToScan( EUser.class.getPackage().getName() );
        factory.setDataSource( dataSource() );
        factory.afterPropertiesSet();

        return factory.getObject();
    }

    @Bean
    public PlatformTransactionManager transactionManager()
    {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory( entityManagerFactory() );
        return txManager;
    }
}

The UserRepository is a simple interface:

public interface UserRepository extends CrudRepository<EUser, Long>
{
}   

The EUser is a simple @Entity annotated class:

@Entity
@Table(name = "user")
public class EUser
{
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Max( value=Integer.MAX_VALUE )
    private Long myId;

    @Column(name = "email")
    @Size(max=64)
    @NotNull
    private String myEmail;

    ...
}

The FillTestData.xml:

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <user id="1"
          email="alice@test.org"
          ...
    />
</dataset>

The DbClean.xml:

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <user />
</dataset>
Heri
  • 4,368
  • 1
  • 31
  • 51
  • Thanks for this article. Can you please assist me in understanding the real benefit of `RepoFactory4Test` class. – Sriram Nov 09 '17 at 06:45
  • 1
    It is one of the two beans declared in the `@ContextConfiguration` annotation in the unit test class. This means spring only loads automagically these two beans for the run of the test. The `RepoFactory4Test` itself provides some further beans which are needed for JPA to work. Beside that it defines which repository to be used (here `@EnableJpaRepositories`) – Heri Nov 09 '17 at 07:43
  • Thanks for the details. However, in my requirement, I'm supposed to load `.sql` files instead of `.xml` files (as mentioned in the above example) and looking for a way I can load them in the test class itself, in this case `ControllerWithRepositoryTest` class. As of now, I've modified the `dataSource()` method from the above example by adding `addScript()` which is loads the sqls. But, looking for a generic solution. Please suggest. – Sriram Nov 15 '17 at 16:03
9

If you're using Spring Boot, you can simplify these approaches a bit by adding @SpringBootTest to load in your ApplicationContext. This allows you to autowire in your spring-data repositories. Be sure to add @RunWith(SpringRunner.class) so the spring-specific annotations are picked up:

@RunWith(SpringRunner.class)
@SpringBootTest
public class OrphanManagementTest {

  @Autowired
  private UserRepository userRepository;

  @Test
  public void saveTest() {
    User user = new User("Tom");
    userRepository.save(user);
    Assert.assertNotNull(userRepository.findOne("Tom"));
  }
}

You can read more about testing in spring boot in their docs.

heez
  • 2,029
  • 3
  • 27
  • 39
2

You cant use repositories in your configuration class because from configuration classes it finds all its repositories using @EnableJpaRepositories.

  1. So change your Java Configuration to:
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan("com.example")
@EnableJpaRepositories(basePackages={"com.example.jpa.repositories"})//Path of your CRUD repositories package
@PropertySource("classpath:application.properties")
public class JPAConfiguration {
  //Includes jpaProperties(), jpaVendorAdapter(), transactionManager(), entityManagerFactory(), localContainerEntityManagerFactoryBean()
  //and dataSource()  
}
  1. If you have many repository implementation classes then create a separate class like below
@Service
public class RepositoryImpl {
   @Autowired
   private UserRepositoryImpl userService;
}
  1. In your controller Autowire to RepositoryImpl and from there you can access all your repository implementation classes.
@Autowired
RepositoryImpl repository;

Usage:

repository.getUserService().findUserByUserName(userName);

Remove @Repository Annotation in ArticleRepository and ArticleServiceImpl should implement ArticleRepository not ArticleService.

sagar
  • 1,269
  • 2
  • 10
  • 10
1

What you need to do is:

  1. remove @Repository from ArticleRepository

  2. add @EnableJpaRepositories to PagesTestConfiguration.java

    @Configuration
    @ComponentScan(basePackages = {"com.example.core"}) // are you sure you wanna scan all the packages?
    @EnableJpaRepositories(basePackageClasses = ArticleRepository.class) // assuming you have all the spring data repo in the same package.
    public class PagesTestConfiguration {
    
    @Bean
    public ArticleServiceImpl articleServiceImpl() {
        ArticleServiceImpl articleServiceImpl = new ArticleServiceImpl();
        return articleServiceImpl;
    }
    }
    
emecas
  • 1,586
  • 3
  • 27
  • 43
Jaiwo99
  • 9,687
  • 3
  • 36
  • 53