Làm cách nào để chế nhạo trường @Value tự động vào mùa xuân với Mockito?


124

Tôi đang sử dụng Spring 3.1.4.RELEASE và Mockito 1.9.5. Trong lớp Spring của tôi, tôi có:

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

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

// ...

Từ thử nghiệm JUnit của tôi, mà tôi hiện đã thiết lập như vậy:

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

Tôi muốn giả một giá trị cho trường "defaultUrl" của mình. Lưu ý rằng tôi không muốn giả mạo các giá trị cho các trường khác - tôi muốn giữ nguyên các giá trị đó, chỉ trường "defaultUrl". Cũng xin lưu ý rằng tôi không có phương thức "setter" rõ ràng (ví dụ setDefaultUrl) trong lớp của mình và tôi không muốn tạo bất kỳ phương thức nào chỉ cho mục đích thử nghiệm.

Với điều này, làm cách nào tôi có thể giả lập một giá trị cho một trường đó?

Câu trả lời:


145

Bạn có thể sử dụng phép thuật của Spring ReflectionTestUtils.setFieldđể tránh thực hiện bất kỳ sửa đổi nào đối với mã của bạn.

Hãy xem hướng dẫn này để biết thêm thông tin, mặc dù có thể bạn sẽ không cần nó vì phương pháp này rất dễ sử dụng

CẬP NHẬT

Kể từ khi giới thiệu Spring 4.2.RC1, bây giờ có thể thiết lập một trường tĩnh mà không cần phải cung cấp một thể hiện của lớp. Xem phần này của tài liệu và cam kết này .


12
Chỉ trong trường hợp liên kết bị chết: sử dụng ReflectionTestUtils.setField(bean, "fieldName", "value");trước khi gọi beanphương thức của bạn trong quá trình kiểm tra.
Michał Stochmal

2
Giải pháp tốt để chế nhạo các thuộc tính đang truy xuất từ ​​tệp thuộc tính.
Antony Sampath Kumar Reddy

@ MichałStochmal, thực hiện điều đó sẽ tạo ra vì được đệ trình là java.lang riêng tư. "" tại org.springframework.util.ReflectionUtils.handleReflectionException (ReflectionUtils.java:112) tại org.springframework.util.ReflectionUtils.setField (ReflectionUtils.java:655)
Akhil Surapuram

113

Bây giờ đã là lần thứ ba tôi tự truy cập vào bài đăng SO này vì tôi luôn quên cách bắt chước trường @Value. Mặc dù câu trả lời được chấp nhận là đúng, tôi luôn cần một khoảng thời gian để thực hiện lệnh gọi "setField" đúng, vì vậy, ít nhất đối với bản thân tôi, tôi dán đoạn mã ví dụ vào đây:

Lớp sản xuất:

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

Lớp kiểm tra:

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

Bạn cũng có thể mô phỏng cấu hình thuộc tính của mình vào lớp thử nghiệm của bạn

@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

Tôi muốn đề xuất một giải pháp liên quan, đó là chuyển các @Valuetrường -annotated dưới dạng tham số cho hàm tạo, thay vì sử dụng ReflectionTestUtilslớp.

Thay vì điều này:

public class Foo {

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

public class FooTest {

    @InjectMocks
    private Foo foo;

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

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

Làm cái này:

public class Foo {

    private String foo;

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

public class FooTest {

    private Foo foo;

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

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

Lợi ích của cách tiếp cận này: 1) chúng tôi có thể khởi tạo lớp Foo mà không cần vùng chứa phụ thuộc (nó chỉ là một hàm tạo) và 2) chúng tôi không ghép bài kiểm tra của chúng tôi với các chi tiết triển khai của chúng tôi (phản ánh liên kết chúng tôi với tên trường bằng cách sử dụng một chuỗi, có thể gây ra sự cố nếu chúng tôi thay đổi tên trường).


3
nhược điểm: Nếu ai đó làm rối với chú thích, ví dụ: sử dụng thuộc tính 'bar' thay vì 'foo', thử nghiệm của bạn sẽ vẫn hoạt động. Tôi chỉ có trường hợp này.
Nils El-Himoud

@ NilsEl-Himoud Nói chung là một điểm hợp lý cho câu hỏi OP, nhưng vấn đề bạn nêu ra không tốt hơn hay tệ hơn bằng cách sử dụng phản xạ utils so với hàm tạo. Điểm của câu trả lời này là xem xét hàm tạo trên phản xạ sử dụng (câu trả lời được chấp nhận). Mark, cảm ơn vì câu trả lời, tôi đánh giá cao sự dễ dàng và sạch sẽ của tinh chỉnh này.
Marquee

22

Bạn có thể sử dụng chú thích Spring Test kỳ diệu này:

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

xem org.springframework.test.context.TestPropertySource

Ví dụ, đây là lớp thử nghiệm:

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

Và đây là lớp có thuộc tính:

@Component
public class MyClass {

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


1

Cũng lưu ý rằng tôi không có phương thức "setter" rõ ràng (ví dụ: setDefaultUrl) trong lớp của mình và tôi không muốn tạo bất kỳ phương thức nào chỉ cho mục đích thử nghiệm.

Một cách để giải quyết điều này là thay đổi lớp của bạn để sử dụng Constructor Injection , được sử dụng để thử nghiệm và tiêm Spring. Không phản ánh nữa :)

Vì vậy, bạn có thể chuyển bất kỳ Chuỗi nào bằng cách sử dụng hàm tạo:

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

}

Và trong thử nghiệm của bạn, chỉ cần sử dụng nó:

MySpringClass MySpringClass  = new MySpringClass("anyUrl", "anyPassword");
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.