Tiêm @ trường riêng mong muốn trong quá trình thử nghiệm


78

Tôi có một thiết lập thành phần về cơ bản là một trình khởi chạy cho một ứng dụng. Nó được cấu hình như vậy:

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    //other methods
}

MyService được chú thích bằng @Servicechú thích Spring và được tự động đưa vào lớp trình khởi chạy của tôi mà không gặp bất kỳ vấn đề nào.

Tôi muốn viết một số trường hợp thử nghiệm jUnit cho MyLauncher, để làm như vậy tôi đã bắt đầu một lớp như thế này:

public class MyLauncherTest
    private MyLauncher myLauncher = new MyLauncher();

    @Test
    public void someTest() {

    }
}

Tôi có thể tạo một đối tượng Mock cho MyService và đưa nó vào myLauncher trong lớp thử nghiệm của mình không? Tôi hiện không có getter hoặc setter trong myLauncher vì Spring đang xử lý autowiring. Nếu có thể, tôi không muốn phải thêm getters và setters. Tôi có thể cho trường hợp thử nghiệm biết để đưa một đối tượng giả vào biến tự động mong muốn bằng @Beforephương thức init không?

Nếu tôi nói về điều này hoàn toàn sai, hãy nói điều đó. Tôi vẫn còn mới với điều này. Mục tiêu chính của tôi là chỉ có một số mã Java hoặc chú thích đặt một đối tượng mô phỏng vào @Autowiredbiến đó mà tôi không cần phải viết phương thức setter hoặc phải sử dụng applicationContext-test.xmltệp. Tôi muốn duy trì mọi thứ cho các trường hợp thử nghiệm trong .javatệp thay vì phải duy trì một nội dung ứng dụng riêng biệt chỉ cho các thử nghiệm của tôi.

Tôi hy vọng sẽ sử dụng Mockito cho các đối tượng giả. Trong quá khứ, tôi đã làm điều này bằng cách sử dụng org.mockito.Mockitovà tạo các đối tượng của tôi với Mockito.mock(MyClass.class).

Câu trả lời:


84

Bạn hoàn toàn có thể đưa các mocks vào MyLauncher trong thử nghiệm của mình. Tôi chắc chắn nếu bạn cho biết bạn đang sử dụng khuôn khổ chế giễu nào thì ai đó sẽ nhanh chóng đưa ra câu trả lời. Với mockito, tôi sẽ xem xét sử dụng @RunWith (MockitoJUnitRunner.class) và sử dụng chú thích cho myLauncher. Nó sẽ trông giống như những gì dưới đây.

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher = new MyLauncher();

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}

@ jpganz18 với junit mà bạn thường sử dụng, nó giống như gọi MockitoAnnotations.initMocks trước mỗi phương thức
fd8s0

Hiện tại, tôi đang sử dụng trình chạy này: @RunWith (SpringRunner.class) Tôi có thể thay thế MockitoJUnitRunner không? Nếu không, có cách nào để tiêm Mocks mà không có nó? (Tôi không thực sự sử dụng các đối tượng giả. Tôi muốn tiêm giống Đậu mà ứng dụng sử dụng.)
MiguelMunoz

56

Câu trả lời được chấp nhận (sử dụng MockitoJUnitRunner@InjectMocks) là tuyệt vời. Nhưng nếu bạn muốn thứ gì đó nhẹ hơn một chút (không có trình chạy JUnit đặc biệt) và ít "ma thuật" hơn (trong suốt hơn) đặc biệt để sử dụng không thường xuyên, bạn có thể đặt các trường riêng tư trực tiếp bằng cách sử dụng nội quan.

Nếu bạn sử dụng Spring, bạn đã có một lớp tiện ích cho việc này: org.springframework.test.util.ReflectionTestUtils

Việc sử dụng khá đơn giản:

ReflectionTestUtils.setField(myLauncher, "myService", myService);

Đối số đầu tiên là bean đích của bạn, đối số thứ hai là tên của trường (thường là riêng tư) và đối số cuối cùng là giá trị để tiêm.

Nếu bạn không sử dụng Spring, việc triển khai một phương thức tiện ích như vậy là khá đơn giản. Đây là mã tôi đã sử dụng trước khi tìm thấy lớp Spring này:

public static void setPrivateField(Object target, String fieldName, Object value){
        try{
            Field privateField = target.getClass().getDeclaredField(fieldName);
            privateField.setAccessible(true);
            privateField.set(target, value);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

1
Câu trả lời chính xác! Tôi đã phải bao gồm mvnrepository.com/artifact/org.springframework/spring-test trong pom.xml của mình để có được lớp ReflectionTestUtils.
user674669

1
Tôi sẽ nói đó là một lời khuyên khủng khiếp. Có ít nhất hai lý do tại sao: 1. bất cứ thứ gì yêu cầu phản chiếu thường có mùi khó chịu và chỉ ra các vấn đề về kiến ​​trúc 2. toàn bộ điểm của việc tiêm phụ thuộc là có thể thay thế đối tượng được tiêm vào một cách dễ dàng, vì vậy một lần nữa, sử dụng phản chiếu bạn đã bỏ qua điểm đó
artkoshelev

@artkoshelev Tôi đồng ý rằng đối với mục đích thử nghiệm và các mối quan tâm về "kiến trúc", construcotr injectionon sẽ sạch hơn và được khuyến nghị. Nó cũng hoạt động tốt hơn với cấu hình java. Nhưng nếu bạn muốn kiểm tra một số đậu hiện có sử dụng tiêm trường và không thể hoặc sẽ không sửa đổi chúng, tôi sẽ tranh luận rằng tốt hơn nên viết một bài kiểm tra sử dụng phản xạ trong thiết lập hơn là không có bài kiểm tra nào ... nếu sự phản chiếu luôn là mùi mã hoặc vấn đề lưu trữ, hơn Spring là mùi mã, Hibernate là mùi mã, v.v.
Pierre Henry

"Bên cạnh đó, nếu sự phản chiếu luôn là mùi mã hoặc vấn đề lưu trữ, hơn Spring là mùi mã, Hibernate là mùi mã, v.v." Tôi chắc chắn không nói về các khung công tác đã được kiểm tra tốt và nổi tiếng, nhưng các nhà phát triển mã ứng dụng thực tế đã viết.
artkoshelev

Tôi thích câu trả lời này hơn.
Andrei Rînea

17

Đôi khi bạn có thể cấu trúc lại cấu trúc của mình @Componentđể sử dụng phương thức tiêm dựa trên phương thức khởi tạo hoặc setter để thiết lập testcase của bạn (bạn có thể và vẫn dựa vào @Autowired). Bây giờ, bạn có thể tạo bài kiểm tra của mình hoàn toàn mà không cần khuôn khổ chế tạo bằng cách triển khai các bài kiểm tra (ví dụ: MailServiceStub của Martin Fowler ):

@Component
public class MyLauncher {

    private MyService myService;

    @Autowired
    MyLauncher(MyService myService) {
        this.myService = myService;
    }

    // other methods
}

public class MyServiceStub implements MyService {
    // ...
}

public class MyLauncherTest
    private MyLauncher myLauncher;
    private MyServiceStub myServiceStub;

    @Before
    public void setUp() {
        myServiceStub = new MyServiceStub();
        myLauncher = new MyLauncher(myServiceStub);
    }

    @Test
    public void someTest() {

    }
}

Kỹ thuật này đặc biệt hữu ích nếu bài kiểm tra và lớp đang kiểm tra nằm trong cùng một gói vì sau đó bạn có thể sử dụng công cụ sửa đổi truy cập gói-riêng tư mặc định để ngăn các lớp khác truy cập vào nó. Lưu ý rằng bạn vẫn có thể có mã sản xuất src/main/javacủa mình nhưng các thử nghiệm của bạn trong src/main/testthư mục.


Nếu bạn thích Mockito thì bạn sẽ đánh giá cao MockitoJUnitRunner . Nó cho phép bạn làm những điều "ma thuật" như @Manuel đã chỉ cho bạn:

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher; // no need to call the constructor

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}

Ngoài ra, bạn có thể sử dụng trình chạy JUnit mặc định và gọi MockitoAnnotations.initMocks () trong một setUp()phương thức để cho phép Mockito khởi tạo các giá trị được chú thích. Bạn có thể tìm thêm thông tin trong javadoc của @InjectMocks và trong một bài đăng trên blog mà tôi đã viết.


Câu trả lời tuyệt vời, phương thức tiêm hàm tạo là cách được ưa thích và hoạt động hiệu quả với Kotlin.
Malkaviano

2

Tôi là người dùng mới của Spring. Tôi đã tìm thấy một giải pháp khác cho điều này. Sử dụng phản chiếu và công khai các trường cần thiết và gán các đối tượng giả.

Đây là bộ điều khiển xác thực của tôi và nó có một số thuộc tính riêng tư Tự động.

@RestController
public class AuthController {

    @Autowired
    private UsersDAOInterface usersDao;

    @Autowired
    private TokensDAOInterface tokensDao;

    @RequestMapping(path = "/auth/getToken", method = RequestMethod.POST)
    public @ResponseBody Object getToken(@RequestParam String username,
            @RequestParam String password) {
        User user = usersDao.getLoginUser(username, password);

        if (user == null)
            return new ErrorResult("Kullanıcıadı veya şifre hatalı");

        Token token = new Token();
        token.setTokenId("aergaerg");
        token.setUserId(1);
        token.setInsertDatetime(new Date());
        return token;
    }
}

Và đây là thử nghiệm Junit của tôi cho AuthController. Tôi đang tạo các thuộc tính riêng tư cần công khai và gán các đối tượng giả cho chúng và đá :)

public class AuthControllerTest {

    @Test
    public void getToken() {
        try {
            UsersDAO mockUsersDao = mock(UsersDAO.class);
            TokensDAO mockTokensDao = mock(TokensDAO.class);

            User dummyUser = new User();
            dummyUser.setId(10);
            dummyUser.setUsername("nixarsoft");
            dummyUser.setTopId(0);

            when(mockUsersDao.getLoginUser(Matchers.anyString(), Matchers.anyString())) //
                    .thenReturn(dummyUser);

            AuthController ctrl = new AuthController();

            Field usersDaoField = ctrl.getClass().getDeclaredField("usersDao");
            usersDaoField.setAccessible(true);
            usersDaoField.set(ctrl, mockUsersDao);

            Field tokensDaoField = ctrl.getClass().getDeclaredField("tokensDao");
            tokensDaoField.setAccessible(true);
            tokensDaoField.set(ctrl, mockTokensDao);

            Token t = (Token) ctrl.getToken("test", "aergaeg");

            Assert.assertNotNull(t);

        } catch (Exception ex) {
            System.out.println(ex);
        }
    }

}

Tôi không biết ưu và nhược điểm của cách này nhưng cách này đang hoạt động. Kỹ thuật này có nhiều mã hơn một chút nhưng các mã này có thể được phân tách bằng các phương pháp khác nhau, v.v. Có nhiều câu trả lời tốt hơn cho câu hỏi này nhưng tôi muốn chỉ ra giải pháp khác. Xin lỗi vì tiếng Anh của tôi không tốt. Chúc mọi người có một java tốt :)


Tôi cũng tương đối mới với mùa xuân. Việc sử dụng @Autowireddường như làm cho việc kiểm tra thực sự đơn giản. Tuy nhiên, tôi cũng không chắc đó có phải là giải pháp "chính xác" hay không.
AnonymousAngelo

Câu hỏi về tiêm
MK-rou

2

Tôi tin rằng để tự động nối dây hoạt động trên lớp MyLauncher của bạn (cho myService), bạn sẽ cần để Spring khởi tạo nó thay vì gọi hàm tạo, bằng cách tự động nối dây myLauncher. Khi nó đang được tự động nối dây (và myService cũng đang được kết nối tự động), Spring (1.4.0 trở lên) cung cấp chú thích @MockBean mà bạn có thể đưa vào thử nghiệm của mình. Điều này sẽ thay thế một đậu đơn phù hợp trong ngữ cảnh bằng một mô hình của loại đó. Sau đó, bạn có thể xác định rõ hơn những gì bạn muốn chế nhạo, trong phương thức @Before.

public class MyLauncherTest
    @MockBean
    private MyService myService;

    @Autowired
    private MyLauncher myLauncher;

    @Before
    private void setupMockBean() {
        doNothing().when(myService).someVoidMethod();
        doReturn("Some Value").when(myService).someStringMethod();
    }

    @Test
    public void someTest() {
        myLauncher.doSomething();
    }
}

Lớp MyLauncher của bạn sau đó có thể vẫn không bị sửa đổi và bean MyService của bạn sẽ là một mô hình có các phương thức trả về giá trị như bạn đã xác định:

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    public void doSomething() {
        myService.someVoidMethod();
        myService.someMethodThatCallsSomeStringMethod();
    }

    //other methods
}

Một vài ưu điểm của phương pháp này so với các phương pháp khác được đề cập là:

  1. Bạn không cần phải tiêm myService theo cách thủ công.
  2. Bạn không cần sử dụng trình chạy hoặc quy tắc Mockito.

Không @MockBeancần @RunWith(SpringRunner.class)?
wonsuc

1

Nhìn vào liên kết này

Sau đó viết trường hợp thử nghiệm của bạn dưới dạng

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/applicationContext.xml"})
public class MyLauncherTest{

@Resource
private MyLauncher myLauncher ;

   @Test
   public void someTest() {
       //test code
   }
}

Đây là cách chính xác để tải Spring Context !! nhưng tôi nghĩ tốt hơn là bạn nên tạo bối cảnh thử nghiệm cho các thử nghiệm của mình ..
Tiarê Balbi

Đây không nhất thiết là cách chính xác, tất cả phụ thuộc vào những gì bạn đang cố gắng hoàn thành. Điều gì sẽ xảy ra nếu bạn đang thiết lập các phiên ngủ đông trong ngữ cảnh ứng dụng của mình? Bạn có thực sự muốn đánh một cơ sở dữ liệu thực với một bài kiểm tra đơn vị? Một số người có thể nói "có", không nhận ra rằng họ đang tạo một bài kiểm tra tích hợp và có thể làm hỏng dữ liệu của họ. Mặt khác, nếu bạn đã tạo bối cảnh ứng dụng thử nghiệm và trỏ chế độ ngủ đông đến cơ sở dữ liệu nhúng thì dữ liệu của bạn sẽ tốt hơn, nhưng bạn vẫn đang tạo thử nghiệm tích hợp, không phải thử nghiệm đơn vị.
Dan

Đây được gọi là "kiểm tra tích hợp" khi bạn muốn kiểm tra các thành phần của mình cùng với toàn bộ ngữ cảnh. Tất nhiên, nó hữu ích, nhưng không giống như unit test khi bạn muốn kiểm tra một lớp riêng lẻ.
Pierre Henry
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.