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?

Dave
quelle

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.

geoand
quelle
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")
BAERUS
quelle
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);
   }
}
Manuel Quinones
quelle
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.

Kennzeichen
quelle
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;
   ...
Thibault
quelle
13

Ich habe den folgenden Code verwendet und es hat bei mir funktioniert:

@InjectMocks
private ClassABC classABC;

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

Referenz: https://www.jeejava.com/mock-an-autowired-value-field-in-spring-with-junit-mockito/

Mendon Ashwini
quelle
1
das gleiche hier ... + 1
Sanfter
Ich habe das Gleiche getan, aber es spiegelt sich immer noch nicht
wider
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");
Dherik
quelle