J'essaie d'écrire un test unitaire pour un simple haricot utilisé dans mon programme pour valider les formulaires. Le bean est annoté avec @Component
et possède une variable de classe initialisée à l'aide de @Value("${this.property.value}") private String thisProperty;
.
J'aimerais écrire des tests unitaires pour les méthodes de validation dans cette classe. Toutefois, si possible, j'aimerais 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, j'aimerais que cela n'affecte pas mon cas de test. Mon cas de test teste le code qui valide la valeur, pas la valeur elle-même.
Existe-t-il un moyen d'utiliser le code Java dans ma classe de test pour initialiser une classe Java et renseigner la propriété Spring @Value dans cette classe, puis l'utiliser à des fins de test?
J'ai trouvé ceci How To qui semble être proche, mais utilise toujours un fichier de propriétés. Je préférerais que tout soit Java code.
Merci
Si possible, j'essayerais d'écrire ces tests sans le contexte Spring. Si vous créez cette classe dans votre test sans ressort, vous avez un contrôle total sur ses champs.
Pour définir le champ @value
, vous pouvez utiliser Ressorts ReflectionTestUtils
- il dispose d'une méthode setField
pour définir des champs privés.
@see JavaDoc: ReflectionTestUtils.setField (Java.lang.Object, Java.lang.String, Java.lang.Object)
Depuis Spring 4.1, vous pouvez définir des valeurs de propriété simplement dans le code en utilisant l'annotation _org.springframework.test.context.TestPropertySource
_ au niveau de la classe de tests unitaires. Vous pouvez utiliser cette approche même pour l'injection de propriétés dans des instances de bean dépendant
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();
}
}
}
_
Remarque: Il est nécessaire d'avoir une instance de _org.springframework.context.support.PropertySourcesPlaceholderConfigurer
_ dans un contexte Spring.
Edit le 24-08-2017: Si vous utilisez SpringBoot 1.4.0 ou une version ultérieure, vous pouvez initialiser les tests avec @SpringBootTest
et @SpringBootConfiguration
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);
}
}
_
Si vous le souhaitez, vous pouvez toujours exécuter vos tests dans Spring Context 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 celui-ci:
La classe sous test:
@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 simplement ajoutée ici pour référence. À mon avis, la méthode la plus efficace consiste à utiliser le coureur Mockito. Dans ce cas, vous n'exécutez pas de tests dans Spring, ce qui est beaucoup plus clair et simple.
Cela semble fonctionner, bien que toujours un peu prolixe (j'aimerais 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");
}
Ne pas abuser des champs privés get/set par réflexion
En utilisant la réflexion comme cela est fait dans plusieurs réponses, voici quelque chose que nous pourrions éviter.
Il apporte une petite valeur ici tout en présentant de multiples inconvénients:
@Value String field
. Demain, vous pourrez déclarer 5
ou 10
dans cette classe et vous risquez même de ne pas être directement conscient du fait que vous diminuez la conception de la classe. Avec une approche plus visible pour définir ces champs (tels que le constructeur), vous réfléchirez à deux fois avant d'ajouter tous ces champs. Vous les encapsulerez probablement dans une autre classe et utiliserez @ConfigurationProperties
.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 de votre classe de composants Spring, vous devez rendre cette classe utilisable avec ou sans Spring.
Exécuter un conteneur dans un test unitaire lorsque cela n’est pas requis est une mauvaise pratique qui ralentit les générations locales: vous ne le souhaitez pas.
J'ai ajouté cette réponse car aucune réponse ne semble montrer cette distinction et ils s'appuient donc systématiquement sur un conteneur en cours d'exécution.
Donc, je pense que vous devriez déplacer cette propriété définie comme un 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 pouvez 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 supplémentaire:
@SpringBootTest
@TestPropertySource("property.value=dummyValue")
public class FooTest{ ...}
Avec Spring (sans Spring Boot), cela devrait être un peu plus compliqué, mais comme je n’ai pas utilisé Spring sans Spring Boot, je ne préfère pas dire une chose stupide.
Remarque: si vous avez beaucoup de champs @Value
à définir, les extraire dans une classe annotée avec @ConfigurationProperties
est plus pertinent car nous ne voulons pas d'un constructeur avec trop d'arguments.
Ajouter PropertyPlaceholderConfigurer à 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 en classe de test
@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());
}
}