web-dev-qa-db-fra.com

Comment puis-je me moquer d'un champ @Value auto-câblé au printemps avec Mockito?

J'utilise Spring 3.1.4.RELEASE et Mockito 1.9.5. Dans ma classe de printemps, j'ai:

@Value("#{myProps['default.url']}")
private String defaultUrl;

@Value("#{myProps['default.password']}")
private String defaultrPassword;

// ...

De mon test JUnit, que j'ai actuellement configuré comme suit:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:test-context.xml" })
public class MyTest 
{ 

Je voudrais me moquer d'une valeur pour mon champ "defaultUrl". Notez que je ne veux pas me moquer des valeurs pour les autres champs - j'aimerais garder celles-ci telles qu'elles sont, seul le champ "defaultUrl". Notez également que je n’ai pas de méthode "setter" explicite (par exemple, setDefaultUrl) dans ma classe et que je ne souhaite pas en créer pour les besoins du test.

Compte tenu de cela, comment puis-je simuler une valeur pour ce champ?

91
Dave

Vous pouvez utiliser la magie de ReflectionTestUtils.setField de Spring afin d'éviter toute modification de votre code.

Consultez le tutoriel this pour plus d'informations, bien que vous n'en ayez probablement pas besoin, car cette méthode est très facile à utiliser

UPDATE

Depuis l'introduction de Spring 4.2.RC1, il est maintenant possible de définir un champ statique sans avoir à fournir une instance de la classe. Voir this une partie de la documentation et this commit.

106
geoand

C’était la troisième fois que je me cherchais moi-même dans ce post SO, car j’ai toujours oublié comment me moquer d’un champ @Value. Bien que la réponse acceptée soit correcte, il me faut toujours un peu de temps pour que l'appel "setField" soit correct. C'est pourquoi, au moins pour moi, je colle un extrait de code ici:

Classe de production:

@Value("#{myProps[‘some.default.url']}")
private String defaultUrl;

Classe de test:

import org.springframework.test.util.ReflectionTestUtils;

ReflectionTestUtils.setField(instanceUnderTest, "defaultUrl", "http://foo");
// Note: Don't use MyClassUnderTest.class, use the instance you are testing itself
// Note: Don't use the referenced string "#{myProps[‘some.default.url']}", 
//       but simply the FIELDs name ("defaultUrl")
77
BAERUS

Vous pouvez également simuler la configuration de votre propriété dans votre classe de test.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:test-context.xml" })
public class MyTest 
{ 
   @Configuration
   public static class MockConfig{
       @Bean
       public Properties myProps(){
             Properties properties = new Properties();
             properties.setProperty("default.url", "myUrl");
             properties.setProperty("property.value2", "value2");
             return properties;
        }
   }
   @Value("#{myProps['default.url']}")
   private String defaultUrl;

   @Test
   public void testValue(){
       Assert.assertEquals("myUrl", defaultUrl);
   }
}
31
Manuel Quinones

J'aimerais suggérer une solution connexe, qui consiste à transmettre les paramètres annotés @Value- en tant que paramètres au constructeur, au lieu d'utiliser la classe ReflectionTestUtils.

Au lieu de cela:

public class Foo {

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

et

public class FooTest {

    @InjectMocks
    private Foo foo;

    @Before
    public void setUp() {
        ReflectionTestUtils.setField(Foo.class, "foo", "foo");
    }

    @Test
    public void testFoo() {
        // stuff
    }
}

Faire ceci:

public class Foo {

    private String foo;

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

et

public class FooTest {

    private Foo foo;

    @Before
    public void setUp() {
        foo = new Foo("foo");
    }

    @Test
    public void testFoo() {
        // stuff
    }
}

Les avantages de cette approche: 1) nous pouvons instancier la classe Foo sans conteneur de dépendances (c'est juste un constructeur), et 2) nous ne couplons pas notre test aux détails de notre implémentation (la réflexion nous lie au nom du champ à l'aide d'une chaîne, ce qui pourrait causer un problème si nous changeons le nom du champ).

14
Mark

Vous pouvez utiliser cette annotation magique Spring Test:

@TestPropertySource(properties = { "my.spring.property=20" }) 

voir org.springframework.test.context.TestPropertySource

Par exemple, voici la classe de test:

@ContextConfiguration(classes = { MyTestClass.Config.class })
@TestPropertySource(properties = { "my.spring.property=20" })
public class MyTestClass {

  public static class Config {
    @Bean
    MyClass getMyClass() {
      return new MyClass ();
    }
  }

  @Resource
  private MyClass myClass ;

  @Test
  public void myTest() {
   ...

Et voici la classe avec la propriété:

@Component
public class MyClass {

  @Value("${my.spring.property}")
  private int mySpringProperty;
   ...
13
Thibault

Notez également que je n’ai pas de méthode "setter" explicite (par exemple, setDefaultUrl) dans ma classe et que je ne souhaite pas en créer pour les besoins du test.

Une façon de résoudre ce problème consiste à changer votre classe pour qu'elle utilise Injection de constructeur, utilisée pour les tests et l’injection de ressort. Pas plus de réflexion :)

Donc, vous pouvez passer n'importe quelle chaîne en utilisant le constructeur:

class MySpringClass {

    private final String defaultUrl;
    private final String defaultrPassword;

    public MySpringClass (
         @Value("#{myProps['default.url']}") String defaultUrl, 
         @Value("#{myProps['default.password']}") String defaultrPassword) {
        this.defaultUrl = defaultUrl;
        this.defaultrPassword= defaultrPassword;
    }

}

Et dans votre test, utilisez-le:

MySpringClass MySpringClass  = new MySpringClass("anyUrl", "anyPassword");
0
Dherik

J'ai utilisé le code ci-dessous et cela a fonctionné pour moi:

@InjectMocks
private AccessFeatureActivationStrategy activationStrategy = new AccessFeatureActivationStrategy();

@Before
public void setUp() {
    ReflectionTestUtils.setField(activationStrategy, "constantFromConfigFile", 3);
}

Référence: https://www.jeejava.com/mock-an-autowired-value-field-in-spring-with-junit-mockito/

0
Mendon Ashwini