Remplissage de la valeur Spring @pendant le test unitaire

J'essaie d'écrire un test unitaire pour un bean simple utilisé dans mon programme pour valider les formulaires. Le bean est annoté avec @Component et a une variable de classe qui est initialisée en utilisant @Value("${this.property.value}") private String thisProperty;

Je voudrais écrire des tests unitaires pour la validation de méthodes à l'intérieur de cette classe, cependant, si possible, je voudrais le faire sans utiliser le fichier de propriétés. Mon raisonnement derrière cela, est que si la valeur que je tire du fichier de propriétés change, je voudrais que cela n'affecte pas mon test cas. Mon cas de test teste le code qui valide la valeur, pas la valeur elle-même.

Existe-t-il un moyen d'utiliser du code Java dans ma classe de test pour initialiser une classe Java et remplir la propriété Spring @Value dans cette classe, puis l'utiliser pour tester avec?

J'ai trouvé ceci Comment {[10] } qui semble être proche, mais utilise toujours un fichier de propriétés. Je préférerais que tout soit du code Java.

Merci

138
demandé sur Kyle 2013-06-28 01:14:41

6 réponses

Si possible, j'essaierais d'écrire ces tests sans contexte de printemps. Si vous créez cette classe dans votre test sans spring, vous avez un contrôle total sur ses champs.

Pour définir la @value champ, vous pouvez utiliser les Ressorts ReflectionTestUtils - il a une méthode setField pour définir les champs privés.

@ Voir JavaDoc: ReflectionTestUtils.setField (java.lang.Objet, java.lang.String, java.lang.Objet)

121
répondu Ralph 2017-01-19 11:52:18

Depuis Spring 4.1, vous pouvez configurer les valeurs de propriété uniquement dans le code en utilisant l'annotation org.springframework.test.context.TestPropertySource au niveau de la classe des tests unitaires. Vous pouvez utiliser cette approche même pour injecter des propriétés dans des instances de bean dépendantes

Par exemple

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = FooTest.Config.class)
@TestPropertySource(properties = {
    "some.bar.value=testValue",
})
public class FooTest {

  @Value("${some.bar.value}")
  String bar;

  @Test
  public void testValueSetup() {
    assertEquals("testValue", bar);
  }


  @Configuration
  static class Config {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertiesResolver() {
        return new PropertySourcesPlaceholderConfigurer();
    }

  }

}

Note: Il est nécessaire d'avoir une instance de org.springframework.context.support.PropertySourcesPlaceholderConfigurer dans le contexte Spring

Edit 24-08-2017: Si vous utilisez SpringBoot 1.4.0 et, plus tard, vous pourriez initialiser des tests avec @SpringBootTest et @SpringBootConfiguration les annotations. Plus d'infos ici

Dans le cas de SpringBoot, nous avons le code suivant

@SpringBootTest
@SpringBootConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@TestPropertySource(properties = {
    "some.bar.value=testValue",
})
public class FooTest {

  @Value("${some.bar.value}")
  String bar;

  @Test
  public void testValueSetup() {
    assertEquals("testValue", bar);
  }

}
97
répondu Dmytro Boichenko 2017-08-24 12:21:38

Si vous le souhaitez, vous pouvez toujours exécuter vos tests dans le contexte Spring et définir les propriétés requises dans la classe de configuration Spring. Si vous utilisez JUnit, utilisez SpringJUnit4ClassRunner et définissez une classe de configuration dédiée pour vos tests comme ceci:

La classe à l'essai:

@Component
public SomeClass {

    @Autowired
    private SomeDependency someDependency;

    @Value("${someProperty}")
    private String someProperty;
}

La classe de test:

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(classes = SomeClassTestsConfig.class)
public class SomeClassTests {

    @Autowired
    private SomeClass someClass;

    @Autowired
    private SomeDependency someDependency;

    @Before
    public void setup() {
       Mockito.reset(someDependency);

    @Test
    public void someTest() { ... }
}

Et la classe de configuration pour ce test:

@Configuration
public class SomeClassTestsConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer properties() throws Exception {
        final PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
        Properties properties = new Properties();

        properties.setProperty("someProperty", "testValue");

        pspc.setProperties(properties);
        return pspc;
    }
    @Bean
    public SomeClass getSomeClass() {
        return new SomeClass();
    }

    @Bean
    public SomeDependency getSomeDependency() {
        // Mockito used here for mocking dependency
        return Mockito.mock(SomeDependency.class);
    }
}

Cela dit, Je ne recommanderais pas cette approche, je l'ai juste ajoutée ici pour référence. Dans mon opinion beaucoup mieux est d'utiliser Mockito runner. Dans ce cas, vous n'exécutez pas du tout de tests à L'intérieur de Spring, ce qui est beaucoup plus clair et plus simple.

42
répondu Lukasz Korzybski 2017-01-24 18:40:30

Cela semble fonctionner, bien que toujours un peu verbeux (je voudrais quelque chose de plus court encore):

@BeforeClass
public static void beforeClass() {
    System.setProperty("some.property", "<value>");
}

// Optionally:
@AfterClass
public static void afterClass() {
    System.clearProperty("some.property");
}
24
répondu john16384 2015-08-10 06:03:13

L'ajout de PropertyPlaceholderConfigurer dans la configuration fonctionne pour moi.

@Configuration
@ComponentScan
@EnableJpaRepositories
@EnableTransactionManagement
public class TestConfiguration {
@Bean
public DataSource dataSource() {
    EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
    builder.setType(EmbeddedDatabaseType.DERBY);
    return builder.build();
}

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
    LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
    entityManagerFactoryBean.setDataSource(dataSource());
    entityManagerFactoryBean.setPackagesToScan(new String[] { "com.test.model" });
    // Use hibernate
    JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
    entityManagerFactoryBean.setJpaProperties(getHibernateProperties());
    return entityManagerFactoryBean;
}

private Properties getHibernateProperties() {
    Properties properties = new Properties();
    properties.put("hibernate.show_sql", "false");
    properties.put("hibernate.dialect", "org.hibernate.dialect.DerbyDialect");
    properties.put("hibernate.hbm2ddl.auto", "update");
    return properties;
}

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

@Bean
PropertyPlaceholderConfigurer propConfig() {
    PropertyPlaceholderConfigurer placeholderConfigurer = new PropertyPlaceholderConfigurer();
    placeholderConfigurer.setLocation(new ClassPathResource("application_test.properties"));
    return placeholderConfigurer;
}

}

Et dans la classe d'essai

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class)
public class DataServiceTest {

@Autowired
private DataService dataService;

@Autowired
private DataRepository dataRepository;

@Value("${Api.url}")
private String baseUrl;

@Test
public void testUpdateData() {
    List<Data> datas = (List<Data>) dataRepository.findAll();
    assertTrue(datas.isEmpty());
    dataService.updateDatas();
    datas = (List<Data>) dataRepository.findAll();
    assertFalse(datas.isEmpty());
}

}

4
répondu fjkjava 2015-09-14 21:30:43

Rendre votre classe testable à la fois unitaire et en intégration

Pour pouvoir écrire à la fois des tests unitaires simples (c'est-à-dire sans conteneur spring en cours d'exécution) et des tests d'intégration pour votre classe de composants Spring, vous devez rendre cette classe utilisable avec ou sans Spring.
Exécuter un conteneur dans un test unitaire quand il n'est pas nécessaire est une mauvaise pratique qui ralentit les builds locaux : vous ne le voulez pas.
J'ai ajouté cette réponse car aucune réponse ici ne semble le montrer distinction et donc ils s'appuient sur un conteneur en cours d'exécution systématiquement.

Donc je pense que vous devriez déplacer cette propriété définie comme interne de la classe:

@Component
public class Foo{   
    @Value("${property.value}") private String property;
    //...
}

Dans un paramètre constructeur qui sera injecté par Spring:

@Component
public class Foo{   
    private String property;

    public Foo(@Value("${property.value}") String property){
       this.property = property;
    }

    //...         
}

Exemple de test unitaire

Vous pouvez instancier Foo sans Spring et injecter n'importe quelle valeur pour property grâce au constructeur:

public class FooTest{

   Foo foo = new Foo("dummyValue");

   @Test
   public void doThat(){
      ...
   }
}

Exemple de test D'intégration

Vous peut-on injecter la propriété dans le contexte avec Spring Boot de cette manière simple grâce à l'attribut properties de @SpringBootTest:

@SpringBootTest(properties="property.value=dummyValue")
public class FooTest{

   @Autowired
   Foo foo;

   @Test
   public void doThat(){
       ...
   }    
}

Vous pouvez utiliser comme alternative @TestPropertySource, mais il ajoute une annotation :

@SpringBootTest
@TestPropertySource("property.value=dummyValue")
public class FooTest{ ...}

Avec Spring (sans Spring Boot), ça devrait être un peu plus compliqué mais comme je n'ai pas utilisé Spring sans Spring Boot depuis longtemps, Je ne préfère pas dire une chose stupide.

4
répondu davidxxx 2018-09-12 18:33:01