373 Stimmen

Füllen von Spring @Value während des Unit Tests

Ich versuche, einen Unittest für einen einfachen Bean zu schreiben, der in meinem Programm verwendet wird, um Formulare zu validieren. Der Bean ist mit @Component annotiert und hat eine Klassenvariable, die mit

@Value("${this.property.value}") private String thisProperty; 

initialisiert wird. Ich möchte Unittests für die Validierungsmethoden innerhalb dieser Klasse schreiben, aber wenn möglich möchte ich dies tun, ohne die properties-Datei zu nutzen. Mein Gedanke dahinter ist, dass sich mein Unittest nicht ändern soll, wenn sich der Wert ändert, den ich aus der properties-Datei hole. Mein Unittest testet den Code, der den Wert validiert, nicht den Wert selbst.

Gibt es eine Möglichkeit, Java-Code in meiner Testklasse zu verwenden, um eine Java-Klasse zu initialisieren und die Spring-@Value-Eigenschaft in dieser Klasse zu befüllen, um damit zu testen?

Ich habe diese Anleitung gefunden, die ähnlich ist, aber immer noch eine properties-Datei verwendet. Ich hätte es lieber komplett in Java-Code.

289voto

Ralph Punkte 114913

Wenn möglich, würde ich versuchen, diese Tests ohne den Spring-Kontext zu schreiben. Wenn Sie diese Klasse in Ihrem Test ohne Spring erstellen, haben Sie die volle Kontrolle über ihre Felder.

Um das @Value-Feld zu setzen, können Sie Springs ReflectionTestUtils verwenden - es hat eine Methode setField zum Setzen privater Felder.

@see JavaDoc: ReflectionTestUtils.setField(java.lang.Object, java.lang.String, java.lang.Object)

260voto

Dmytro Boichenko Punkte 4857

Seit Spring 4.1 können Sie Property-Werte nur im Code einrichten, indem Sie die org.springframework.test.context.TestPropertySource-Annotation auf Klassenebene von Unit-Tests verwenden. Sie können diesen Ansatz sogar für das Einfügen von Eigenschaften in abhängige Bean-Instanzen verwenden

Zum Beispiel

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

  }

}

Hinweis: Es ist notwendig, eine Instanz von org.springframework.context.support.PropertySourcesPlaceholderConfigurer im Spring-Kontext zu haben

Edit 24-08-2017: Wenn Sie SpringBoot 1.4.0 und höher verwenden, können Sie Tests mit den Annotationen @SpringBootTest und @SpringBootConfiguration initialisieren. Weitere Informationen finden Sie hier

Im Fall von SpringBoot haben wir folgenden Code

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

}

167voto

davidxxx Punkte 114488

Missbrauchen Sie private Felder nicht mit Reflection get/set

Die Verwendung von Reflection, wie sie in mehreren Antworten hier getan wird, ist etwas, das wir vermeiden könnten.
Es bringt hier einen geringen Wert mit sich, während es mehrere Nachteile birgt :

  • Wir erkennen Reflexionsprobleme nur zur Laufzeit (z. B. Felder existieren nicht mehr)
  • Wir wollen Kapselung, aber keine undurchsichtige Klasse, die Abhängigkeiten verbirgt, die sichtbar sein sollten und die Klasse undurchsichtiger und weniger testbar macht.
  • Es fördert schlechtes Design. Heute deklarieren Sie ein @Value String field. Morgen können Sie 5 oder 10 von ihnen in dieser Klasse deklarieren und Sie sind sich möglicherweise nicht einmal bewusst, dass Sie das Design der Klasse mindern. Mit einem sichtbareren Ansatz, um diese Felder zu setzen (zum Beispiel im Konstruktor), werden Sie zweimal darüber nachdenken, bevor Sie all diese Felder hinzufügen, und wahrscheinlich werden Sie sie in eine andere Klasse kapseln und @ConfigurationProperties verwenden.

Machen Sie Ihre Klasse sowohl unitär als auch in der Integration testbar

Um sowohl einfache Tests (ohne einen laufenden Spring-Container) als auch Integrationstests für Ihre Spring-Komponentenklasse schreiben zu können, müssen Sie diese Klasse verwendbar mit oder ohne Spring machen.
Das Ausführen eines Containers in einem Unittest, wenn es nicht erforderlich ist, ist eine schlechte Praxis, die lokale Builds verlangsamt: Das wollen Sie nicht.
Ich habe diese Antwort hinzugefügt, weil keine Antwort hier diesen Unterschied zu zeigen scheint und daher systematisch auf einen laufenden Container angewiesen ist.

Also denke ich, dass Sie dieses als intern in der Klasse definierte Property :

@Component
public class Foo{   
    @Value("${property.value}") private String property;
    //...
}

in ein Konstruktorparameter verschieben sollten, der von Spring injiziert wird :

@Component
public class Foo{   
    private String property;

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

    //...         
}

Beispiel für Unittest

Sie können Foo ohne Spring instanziieren und jedem Wert für property über den Konstruktor injizieren :

public class FooTest{

   Foo foo = new Foo("dummyValue");

   @Test
   public void doThat(){
      ...
   }
}

Beispiel für Integrationstest

Sie können das Property im Kontext mit Spring Boot auf einfache Weise dank des properties-Attributs von @SpringBootTest injizieren :

@SpringBootTest(properties="property.value=dummyValue")
public class FooTest{

   @Autowired
   Foo foo;

   @Test
   public void doThat(){
       ...
   }    
}

Sie könnten alternativ @TestPropertySource verwenden, aber es fügt eine zusätzliche Annotation hinzu :

@SpringBootTest
@TestPropertySource(properties="property.value=dummyValue")
public class FooTest{ ...}

Mit Spring (ohne Spring Boot) wird es etwas komplizierter sein, aber da ich schon lange kein Spring ohne Spring Boot mehr benutzt habe, ziehe ich es vor, keinen Unsinn zu erzählen.

Als Nebenbemerkung: Wenn Sie viele @Value-Felder setzen müssen, ist es sinnvoller, sie in eine Klasse zu extrahieren, die mit @ConfigurationProperties annotiert ist, da wir keinen Konstruktor mit zu vielen Argumenten wollen.

58voto

Lukasz Korzybski Punkte 6579

Wenn Sie möchten, können Sie Ihre Tests immer noch innerhalb des Spring-Kontexts ausführen und die erforderlichen Eigenschaften innerhalb der Spring-Konfigurationsklasse festlegen. Wenn Sie JUnit verwenden, verwenden Sie SpringJUnit4ClassRunner und definieren Sie eine dedizierte Konfigurationsklasse für Ihre Tests wie folgt:

Die Klasse unter Test:

@Component
public SomeClass {

    @Autowired
    private SomeDependency someDependency;

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

Die Testklasse:

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

Und die Konfigurationsklasse für diesen 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 hier verwendet für das Mocking der Abhängigkeit
        return Mockito.mock(SomeDependency.class);
    }
}

Das gesagt habend, würde ich diesen Ansatz nicht empfehlen, ich habe ihn nur als Referenz hinzugefügt. Meiner Meinung nach ist der Einsatz des Mockito-Runners ein viel besserer Ansatz. In diesem Fall führen Sie die Tests überhaupt nicht innerhalb von Spring aus, was viel klarer und einfacher ist.

34voto

john16384 Punkte 7220

Dies scheint zu funktionieren, obwohl es immer noch ein bisschen umständlich ist (ich hätte gerne etwas kürzeres):

@BeforeClass
public static void beforeClass() {
    System.setProperty("some.property", "");
}

// Optional:
@AfterClass
public static void afterClass() {
    System.clearProperty("some.property");
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X