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
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)
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);
}
}
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.
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");
}
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());
}
}
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.