• Home
  • |
  • Hướng dẫn mô hình MVVM trong ios kỳ 3: Unit Test ViewModel

Tháng Mười 4, 2019

Hướng dẫn mô hình MVVM trong ios kỳ 3: Unit Test ViewModel

Download source sample tại đây.

Xem tổng quan và nội dung của series tại đây.

Xin chào các bạn nhé! 😃 Ở bài viết trước chúng ta đã hiện thực một ví dụ đơn giản bằng mô hình MVVM. Hôm nay chúng ta sẽ tiếp tục bằng việc gắn Unit Test cho demo hiện tại.

Khi thực hành xong bài viết này các bạn sẽ nắm được cách thực hiện unit test phần ViewModel trong mô hình MVVM trên iOS. Ngoài ra mình cũng sẽ hướng dẫn các bạn cách dùng check Code Coverage bằng xcode để xem unit test của mình đã bao phủ toàn bộ code chưa.

Ý tưởng

Chúng ta sẽ bắt đầu bằng việc nhắc lại một chút về SampleViewModel. Trong SampleViewModel, có một hàm là callAPI. Khi gọi hàm này thì:

  • Tiến hành gửi http request
  • Nếu thành công thì thay đổi trạng thái thuộc tính isOn là true.
  • Nếu không thì không làm gì cả.

Như vậy chúng ta có 2 case như sau:

  • Gọi api thành công: Khi gọi api thành công thì thuộc tính isOn được thay đổi
  • Gọi api thất bại: Khi gọi api thành công thì thuộc tính isOn vẫn như cũ

OK ý tưởng đã có, chúng ta bắt đầu xắn tay làm thôi 😃.

Test case gọi api thành công

Mở file SampleViewModelTests, trong hàm testSuccess chèn đoạn code sau:

// Give
// 1
let descriptor = stub(condition: isHost("httpbin.org")) { _ in
    return OHHTTPStubsResponse(data: Data(), statusCode: 200, headers: nil).responseTime(0.5)
}
// 2
let sample = SampleViewModel()
sample.isOn.silentUpdate(value: true)

// When
// 3
sample.callAPI()

// 4
let expectation = self.expectation(description: "testSuccess")

// 5
sample.isOn.skip(first: 1) .observeNext { (value: Bool) in
    // 6
    expectation.fulfill()
}.dispose(in: bag)

// Then
// 7
waitForExpectations(timeout: 1)

// 8
XCTAssertEqual(sample.isOn.value, false)

// 9
OHHTTPStubs.removeStub(descriptor)

Phần mình comment “Give, When, Then” bạn nào thắc mắc thì đây là cách phân chia các thành phần của unit test. Các bạn có thể khảo thêm tại đây. Nếu bạn muốn hướng dẫn thêm trên swift thì có thể xem tại đây.

Rồi giờ thì đến phần nội dung chính của code:

  1. Giả lập http request để dễ dàng test. Trong trường hợp này chúng ta sử dung thư viện OHHTTPStubs để giả lập. Test case này giả lập cho khi request network thì kết quả trả về luôn là thành công và thời gian chờ là 0.5 giây.
  2. Tạo và config đối tượng SampleViewModel để test.
  3. Gọi hàm callAPI để test.
  4. Tạo expectation dùng để unit test cho trường hợp asynchronous unit test. Để biết rõ, bạn có thể xem tại đây.
  5. Gắn observer để bắt sự kiện thuộc tính isOn thay đổi. Trong đó việc gọi hàm skip(first: 1) nhằm mục đích để bỏ qua sự kiện đầu tiên. Vì khi gắn observer vào thì thuộc tính isOn sẽ tự động trigger sự kiện đầu tiên với giá trị là giá trị hiện tại.
  6. Gọi hàm full fill expectation dành cho asynchronous unit test.
  7. Ngừng việc thực thi test case để chờ cho expectation được fulfill.
  8. Assert check điều kiện của unit test.
  9. Tháo giả lập mạng ra để reset trạng thái test về trạng thái ban đầu.

Nếu bạn chạy thử chưa được thì đó là do bị thiếu cài đặt và import framework OHHTTPStubs. Để cài đặt các bạn thêm dòng này trong podfile và chạy lệnh pod install:

target 'MVVM_SampleTests' do
    inherit! :search_paths
    # Pods for testing
    pod 'OHHTTPStubs/Swift'
end

Sau đó trong file SampleModelViewTests ở phần import thêm vào:

import OHHTTPStubs

Chạy thử và bạn sẽ thấy thông báo kết quả thành công.

Code Coverage

Ok bây giờ mình sẽ xem phần code coverage để đánh giá độ phủ của unit test

Trong xcode, nhấn vào biểu tượng mà log mấy cái log build, test này nọ.

À mà khoan! trước tiên cần vào scheme tích vào cái mục code coverage để xcode enable cái đó lên nha bạn. 😃

Các bạn vào menu Product > Scheme > Edit Scheme. Ở trong màn hình Edit Scheme, nhìn qua cột bên trái chọn mục Test. Tiếp theo ở phần chi tiết bên tay phải chọn menu item Options, ở phía dưới tick vào mục Code Coverage như hình dưới đây:

Xong rồi bạn nhấn chạy lại unit test, rồi lại nhấn vào cái biểu tượng log, ngay chỗ để biểu tượng thư mục rồi tìm kiếm này nọ trên thanh side bar. Đó là biểu tượng cuối cùng bên tay phải. Các bạn có thể xem hình dưới đây để dễ kiếm hơn:

Nhấn vào nhìn cái list ở dưới bạn cái mục code coverage. Nhấn vào bạn sẽ thấy thống kê các unit test đã chạy được bao nhiêu phần trăm rồi:

Như trong kết quả test ở trên thì SampleViewModel chưa full 100%, mà còn bị thiếu một chút xíu xìu xiu nữa. Các bạn hãy nhấn đúp vô cái file. Nó sẽ nhảy vào cái file và trỏ vô đoạn code mà unit test chưa chạy qua, mà tô màu luôn đó nha, hơi bị dễ nhìn 😃. Nhìn vô chỗ bôi thì bạn thấy đó là unit test còn thiếu cho trường hợp request api bị thất bại.

Bây giờ chúng ta sẽ tiến hành bổ sung thêm unit test để cho phần unit test của SampleViewModel được full 100% luôn 😃!

Test case gọi api thất bại

Tiếp tục class SampleViewModelTests, trong hàm testFail thêm đoạn code này vô:

// Give
let descriptor = stub(condition: isHost("httpbin.org")) { _ in
    return OHHTTPStubsResponse(error: AFError.explicitlyCancelled).responseTime(0.5)
}
let sample = SampleViewModel()
sample.isOn.silentUpdate(value: true)

// When
sample.callAPI()
let expectation = self.expectation(description: "testFail")
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(600)) {
    expectation.fulfill()
}

// Then
waitForExpectations(timeout: 1)
XCTAssertEqual(sample.isOn.value, true)
OHHTTPStubs.removeStub(descriptor)

Trong này thì nhìn chung cũng giống y chang như cái test case ở trên, chỉ khác ở chỗ là phần giả lập mạng ở vd trước là luôn thành công. Lần này, mình giả cho nó luôn thất bại.

Và ở phần data binding thì mình bỏ đi, không binding nữa tại vì nếu api thất bại đâu có làm gì đâu. Nên binding cũng không được ích lợi gì. Bởi vậy nên mình thay vào đó là chờ sau khi request api xong thì check thẳng vào cai thuộc tính isOn luôn, nếu nó vẫn là true tức là chạy đúng rồi 😃.

Lần này bạn hãy chạy thử lại lần nữa rồi vô check phần code coverage lại:

Ta đa! Các bạn thấy hay chưa 😄? Code coverage của SampleViewModel đã là 100% rồi đúng không các bạn? Rất đơn giản đúng không nào?

Tổng kết

Ok thì đến đây là chúng ta đã hoàn tất việc unit test cho SampleViewModel rồi. Các bạn có thể thấy là mình đã test xong toàn bộ logic của SampleViewController thông qua SampleViewModel mà không sợ bị sót logic ở bất cứ chỗ nào cả. Đây chính là một trong những điểm hay của MVVM. 😃

Ơ thế còn SampleViewController thì chúng ta test thế nào đây?

Các bạn cứ từ từ, chuyện đâu còn có đó 😃. Unit test cho SampleViewController sẽ được trình bày trong bài viết tiếp theo các bạn nhé! Rất mong các bạn chú ý theo dõi và đón xem. Xin cám ơn và chào tạm biệt các bạn. Hẹn gặp lại các bạn trong bài viết tiếp theo. Bái bai! 😄

Related Posts

Custom text input trong Eureka

Custom picker row trong Eureka

Hướng dẫn cài đặt React Native đơn giản bao chạy được tháng 01/2020

Hướng dẫn mô hình MVVM trong ios kỳ 4: Dependency Injection và Unit Test ViewController

Hiển Phạm


Mình là một lập trình viên ios. Khi rảnh rỗi mình thích chơi game, đọc sách và tìm hiểu nhiều hơn về kỹ thuật lập trình.

Your Signature

Leave a Reply


Your email address will not be published. Required fields are marked

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}