Wie verspotte ich mit Mockito ein automatisch verdrahtetes @ Value-Feld im Frühjahr?


123

Ich benutze Spring 3.1.4.RELEASE und Mockito 1.9.5. In meiner Frühlingsklasse habe ich:

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

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

// ...

Aus meinem JUnit-Test, den ich derzeit so eingerichtet habe:

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

Ich möchte einen Wert für mein Feld "defaultUrl" verspotten. Beachten Sie, dass ich keine Werte für die anderen Felder verspotten möchte - ich möchte diese so lassen, wie sie sind, nur das Feld "defaultUrl". Beachten Sie auch, dass ich setDefaultUrlin meiner Klasse keine expliziten "Setter" -Methoden (z. B. ) habe und keine nur zu Testzwecken erstellen möchte.

Wie kann ich vor diesem Hintergrund einen Wert für dieses eine Feld verspotten?

Antworten:


144

Sie können die Magie von Spring nutzen ReflectionTestUtils.setField, um Änderungen an Ihrem Code zu vermeiden.

Schauen Sie sich dieses Tutorial für noch mehr Informationen, obwohl Sie wahrscheinlich nicht brauchen , da das Verfahren sehr einfach zu bedienen ist

AKTUALISIEREN

Seit der Einführung von Spring 4.2.RC1 ist es nun möglich, ein statisches Feld festzulegen, ohne eine Instanz der Klasse angeben zu müssen. Siehe diesen Teil der Dokumentation und dieses Commit.


12
Nur für den Fall, dass der Link tot ist: Verwenden Sie diese ReflectionTestUtils.setField(bean, "fieldName", "value");Option, bevor Sie Ihre beanMethode während des Tests aufrufen .
Michał Stochmal

2
Gute Lösung zum Verspotten der Eigenschaften, die aus der Eigenschaftendatei abgerufen werden.
Antony Sampath Kumar Reddy

@ MichałStochmal, dies wird erzeugt, da abgelegt ist private java.lang.IllegalStateException: Zugriff auf Methode: Klasse org.springframework.util.ReflectionUtils kann nicht auf ein Mitglied der Klasse com.kaleidofin.app.service.impl.CVLKRAProvider mit Modifikatoren zugreifen "" unter org.springframework.util.ReflectionUtils.handleReflectionException (ReflectionUtils.java:112) unter org.springframework.util.ReflectionUtils.setField (ReflectionUtils.java:655)
Akhil Surapuram

112

Es war jetzt das dritte Mal, dass ich mich in diesen SO-Beitrag gegoogelt habe, da ich immer vergesse, wie man ein @ Value-Feld verspottet. Obwohl die akzeptierte Antwort korrekt ist, brauche ich immer etwas Zeit, um den "setField" -Aufruf richtig zu machen. Zumindest für mich selbst füge ich hier ein Beispiel-Snippet ein:

Produktionsklasse:

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

Testklasse:

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

Sie können Ihre Eigenschaftskonfiguration auch in Ihrer Testklasse verspotten

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

24

Ich möchte eine verwandte Lösung vorschlagen, bei der die mit @Value-annotierten Felder als Parameter an den Konstruktor übergeben werden, anstatt die ReflectionTestUtilsKlasse zu verwenden.

An Stelle von:

public class Foo {

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

und

public class FooTest {

    @InjectMocks
    private Foo foo;

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

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

Mach das:

public class Foo {

    private String foo;

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

und

public class FooTest {

    private Foo foo;

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

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

Vorteile dieses Ansatzes: 1) Wir können die Foo-Klasse ohne einen Abhängigkeitscontainer instanziieren (es ist nur ein Konstruktor), und 2) wir koppeln unseren Test nicht mit unseren Implementierungsdetails (Reflexion bindet uns mit einer Zeichenfolge an den Feldnamen). Dies könnte ein Problem verursachen, wenn wir den Feldnamen ändern.


3
Nachteil: Wenn jemand mit der Anmerkung herumspielt, z. B. eine Eigenschaftsleiste anstelle von "foo" verwendet, funktioniert Ihr Test weiterhin. Ich habe nur diesen Fall.
Nils El-Himoud

@ NilsEl-Himoud Das ist im Allgemeinen ein fairer Punkt für die OP-Frage, aber das Problem, das Sie ansprechen, ist nicht besser oder schlechter dran, wenn Sie Reflection Utils gegen Konstruktor verwenden. Der Punkt dieser Antwort war die Berücksichtigung des Konstruktors gegenüber der Reflexion util (die akzeptierte Antwort). Mark, danke für die Antwort, ich schätze die Leichtigkeit und Sauberkeit dieses Tweaks.
Marquee

22

Sie können diese magische Anmerkung zum Frühlingstest verwenden:

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

Siehe org.springframework.test.context.TestPropertySource

Dies ist beispielsweise die Testklasse:

@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() {
   ...

Und das ist die Klasse mit der Eigenschaft:

@Component
public class MyClass {

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


1

Beachten Sie auch, dass ich in meiner Klasse keine expliziten "Setter" -Methoden (z. B. setDefaultUrl) habe und keine nur zu Testzwecken erstellen möchte.

Eine Möglichkeit, dies zu beheben, besteht darin, Ihre Klasse so zu ändern , dass die Konstruktorinjektion verwendet wird, die zum Testen und zur Federinjektion verwendet wird. Keine Reflexion mehr :)

Sie können also einen beliebigen String mit dem Konstruktor übergeben:

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

}

Und in Ihrem Test verwenden Sie es einfach:

MySpringClass MySpringClass  = new MySpringClass("anyUrl", "anyPassword");
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.