Come faccio a deridere un campo @Value autowired in primavera con Mockito?


124

Sto usando Spring 3.1.4.RELEASE e Mockito 1.9.5. Nella mia classe primaverile ho:

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

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

// ...

Dal mio test JUnit, che attualmente ho impostato in questo modo:

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

Vorrei deridere un valore per il mio campo "defaultUrl". Nota che non voglio deridere i valori per gli altri campi: vorrei mantenerli così come sono, solo il campo "defaultUrl". Notare anche che non ho metodi "setter" espliciti (ad esempio setDefaultUrl) nella mia classe e non voglio crearne nessuno solo per scopi di test.

Detto questo, come posso deridere un valore per quel campo?

Risposte:


145

Puoi usare la magia di Spring's ReflectionTestUtils.setFieldper evitare di apportare modifiche di sorta al tuo codice.

Dai un'occhiata a questo tutorial per ulteriori informazioni, anche se probabilmente non ne avrai bisogno poiché il metodo è molto facile da usare

AGGIORNARE

Dall'introduzione della Spring 4.2.RC1 è ora possibile impostare un campo statico senza dover fornire un'istanza della classe. Vedi questa parte della documentazione e questo commit.


12
Solo in caso di collegamento morto: utilizzare ReflectionTestUtils.setField(bean, "fieldName", "value");prima di invocare il beanmetodo durante il test.
Michał Stochmal

2
Buona soluzione per deridere le proprietà che vengono recuperate dal file delle proprietà.
Antony Sampath Kumar Reddy

@ MichałStochmal, ciò produrrà poiché il file è privato java.lang.IllegalStateException: Impossibile accedere al metodo: la classe org.springframework.util.ReflectionUtils non può accedere a un membro della classe com.kaleidofin.app.service.impl.CVLKRAProvider con modificatori "" in org.springframework.util.ReflectionUtils.handleReflectionException (ReflectionUtils.java:112) in org.springframework.util.ReflectionUtils.setField (ReflectionUtils.java:655)
Akhil Surapuram

113

Era la terza volta che cercavo su Google questo post SO perché dimentico sempre come deridere un campo @Value. Sebbene la risposta accettata sia corretta, ho sempre bisogno di un po 'di tempo per ottenere la chiamata "setField", quindi almeno per me stesso incollo uno snippet di esempio qui:

Classe di produzione:

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

Classe di prova:

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")

32

Puoi anche simulare la configurazione della tua proprietà nella tua classe di 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);
   }
}

25

Vorrei suggerire una soluzione correlata, che consiste nel passare i @Valuecampi -annotated come parametri al costruttore, invece di usare la ReflectionTestUtilsclasse.

Invece di questo:

public class Foo {

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

e

public class FooTest {

    @InjectMocks
    private Foo foo;

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

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

Fai questo:

public class Foo {

    private String foo;

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

e

public class FooTest {

    private Foo foo;

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

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

Vantaggi di questo approccio: 1) possiamo istanziare la classe Foo senza un contenitore di dipendenze (è solo un costruttore) e 2) non stiamo accoppiando il nostro test ai nostri dettagli di implementazione (la riflessione ci lega al nome del campo usando una stringa, che potrebbe causare un problema se cambiamo il nome del campo).


3
svantaggio: se qualcuno scherza con l'annotazione, ad esempio utilizza una proprietà "bar" invece di "foo", il test continuerà a funzionare. Ho solo questo caso.
Nils El-Himoud

@ NilsEl-Himoud Questo è un punto giusto in generale per la domanda OP, ma il problema che sollevi non è né migliore né peggiore usando la riflessione utils vs costruttore. Il punto di questa risposta era la considerazione del costruttore rispetto alla riflessione util (la risposta accettata). Marco, grazie per la risposta, apprezzo la facilità e la pulizia di questo tweak.
Marquee

22

Puoi usare questa magica annotazione del test di primavera:

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

vedere org.springframework.test.context.TestPropertySource

Ad esempio, questa è la classe di 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() {
   ...

E questa è la classe con la proprietà:

@Component
public class MyClass {

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


1

Si noti inoltre che non ho metodi "setter" espliciti (ad esempio setDefaultUrl) nella mia classe e non voglio crearne nessuno solo per scopi di test.

Un modo per risolvere questo problema è cambiare la classe per utilizzare Constructor Injection , che viene utilizzato per il test e l'iniezione di Spring. Niente più riflessioni :)

Quindi, puoi passare qualsiasi stringa usando il costruttore:

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;
    }

}

E nel tuo test, usalo semplicemente:

MySpringClass MySpringClass  = new MySpringClass("anyUrl", "anyPassword");
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.