Como faço para simular um campo @Value autowired no Spring com o Mockito?

124

Estou usando Spring 3.1.4.RELEASE e Mockito 1.9.5. Na minha aula de primavera eu tenho:

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

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

// ...

Do meu teste JUnit, que atualmente configurei assim:

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

Eu gostaria de simular um valor para meu campo "defaultUrl". Observe que não quero simular valores para os outros campos - gostaria de mantê-los como estão, apenas o campo "defaultUrl". Observe também que não tenho métodos "setter" explícitos (por exemplo setDefaultUrl) em minha classe e não quero criar nenhum apenas para fins de teste.

Diante disso, como posso simular um valor para aquele campo?

Dave
fonte

Respostas:

145

Você pode usar a magia do Spring ReflectionTestUtils.setFieldpara evitar fazer qualquer modificação em seu código.

Confira este tutorial para obter ainda mais informações, embora você provavelmente não precise, pois o método é muito fácil de usar

ATUALIZAR

Desde a introdução do Spring 4.2.RC1, agora é possível definir um campo estático sem ter que fornecer uma instância da classe. Veja esta parte da documentação e este commit.

geoand
fonte
12
Apenas no caso de link estar morto: use ReflectionTestUtils.setField(bean, "fieldName", "value");antes de invocar seu beanmétodo durante o teste.
Michał Stochmal
2
Boa solução para simular as propriedades que estão sendo recuperadas do arquivo de propriedades.
Antony Sampath Kumar Reddy
@ MichałStochmal, fazer isso produzirá uma vez que o arquivado é java.lang.IllegalStateException: Método não foi possível acessar: A classe org.springframework.util.ReflectionUtils não pode acessar um membro da classe com.kaleidofin.app.service.impl.CVLKRAProvider com modificadores "" em org.springframework.util.ReflectionUtils.handleReflectionException (ReflectionUtils.java:112) em org.springframework.util.ReflectionUtils.setField (ReflectionUtils.java:655)
Akhil Surapuram
113

Agora foi a terceira vez que eu pesquisei este post do SO, já que sempre esqueço como simular um campo @Value. Embora a resposta aceita esteja correta, sempre preciso de algum tempo para fazer a chamada "setField" certa, então, pelo menos para mim, colo um snippet de exemplo aqui:

Aula de produção:

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

Classe de teste:

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
fonte
32

Você também pode simular a configuração de sua propriedade em sua classe de teste

@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
fonte
25

Eu gostaria de sugerir uma solução relacionada, que é passar os @Valuecampos anotados como parâmetros para o construtor, em vez de usar a ReflectionTestUtilsclasse.

Em vez disso:

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

Faça isso:

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

Benefícios dessa abordagem: 1) podemos instanciar a classe Foo sem um contêiner de dependência (é apenas um construtor) e 2) não estamos acoplando nosso teste aos detalhes de implementação (a reflexão nos liga ao nome do campo usando uma string, o que pode causar um problema se alterarmos o nome do campo).

Marca
fonte
3
desvantagem: se alguém bagunçar a anotação, por exemplo, usar uma propriedade 'bar' em vez de 'foo', seu teste ainda funcionará. Eu só tenho esse caso.
Nils El-Himoud
@ NilsEl-Himoud Esse é um ponto justo em geral para a questão do OP, mas a questão que você levantou não é melhor ou pior usando utilitários de reflexão versus construtor. O ponto desta resposta foi a consideração do construtor sobre a reflexão util (a resposta aceita). Mark, obrigado pela resposta, agradeço a facilidade e a limpeza desse ajuste.
Marquee de
22

Você pode usar esta anotação mágica do Spring Test:

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

consulte org.springframework.test.context.TestPropertySource

Por exemplo, esta é a classe de teste:

@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 esta é a classe com a propriedade:

@Component
public class MyClass {

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

Usei o código abaixo e funcionou para mim:

@InjectMocks
private ClassABC classABC;

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

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

Mendon Ashwini
fonte
1
mesmo aqui ... + 1
Suave
Eu meio que fiz o mesmo, mas ainda não está refletindo
Shubhro Mukherjee
1

Observe também que não tenho métodos "setter" explícitos (por exemplo, setDefaultUrl) em minha classe e não quero criar nenhum apenas para fins de teste.

Uma maneira de resolver isso é mudar sua classe para usar Injeção de Construtor , que é usada para teste e injeção de Spring. Sem mais reflexão :)

Portanto, você pode passar qualquer String usando o construtor:

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 em seu teste, basta usá-lo:

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