Сегодня я снова столкнулся с одной проблемой объединения, и надеюсь, что кто-то из вас сможет помочь Как можно написать обычные модульные тесты для классов ObservableObjects, которые содержат атрибуты @Published? Как я могу подписаться в своем тесте на них, чтобы получить объект результата, который я могу утверждать?
Инжектированный макет для веб-службы работает правильно, функция loadProducts()
устанавливает точно такие же элементы из макета в fetchedProducts
массив.
Но в настоящее время я не знаю, как получить доступ к этому массиву в моем тесте после того, как он заполнен функцией, потому что кажется, что я не могу работать с ожиданиями, loadProducts()
не имеет блока завершения.
Код выглядит так:
class ProductsListViewModel: ObservableObject {
let getRequests: GetRequests
let urlService: ApiUrls
private let networkUtils: NetworkRequestUtils
let productsWillChange = ObservableObjectPublisher()
@Published var fetchedProducts = [ProductDTO]()
@Published var errorCodeLoadProducts: Int?
init(getRequestsHelper: GetRequests, urlServiceClass: ApiUrls = ApiUrls(), utilsNetwork: NetworkRequestUtils = NetworkRequestUtils()) {
getRequests = getRequestsHelper
urlService = urlServiceClass
networkUtils = utilsNetwork
}
// nor completion block in the function used
func loadProducts() {
let urlForRequest = urlService.loadProductsUrl()
getRequests.getJsonData(url: urlForRequest) { [weak self] (result: Result<[ProductDTO], Error>) in
self?.isLoading = false
switch result {
case .success(let productsArray):
// the products filled async here
self?.fetchedProducts = productsArray
self?.errorCodeLoadProducts = nil
case .failure(let error):
let errorCode = self?.networkUtils.errorCodeFrom(error: error)
self?.errorCodeLoadProducts = errorCode
print("error: \(error)")
}
}
}
}
На данный момент тест, который я пытаюсь написать, выглядит следующим образом:
import XCTest
@testable import MyProject
class ProductsListViewModelTest: XCTestCase {
var getRequestMock: GetRequests!
let requestManagerMock = RequestManagerMockLoadProducts()
var productListViewModel: ProductsListViewModel!
override func setUp() {
super.setUp()
getRequestMock = GetRequests(networkHelper: requestManagerMock)
productListViewModel = ProductsListViewModel(getRequestsHelper: getRequestMock)
}
func test_successLoadProducts() {
let loginDto = LoginResponseDTO(token: "token-token")
UserDefaults.standard.save(loginDto, forKey: CommonConstants.persistedLoginObject)
productListViewModel.loadProducts()
// TODO access the fetchedProducts here somehow and assert them
}
}
Мок выглядит так:
class RequestManagerMockLoadProducts: NetworkRequestManagerProtocol {
var isSuccess = true
func makeNetworkRequest<T>(urlRequestObject: URLRequest, completion: @escaping (Result<T, Error>) -> Void) where T : Decodable {
if isSuccess {
let successResultDto = returnedProductedArray() as! T
completion(.success(successResultDto))
} else {
let errorString = "Cannot create request object here"
let error = NSError(domain: ErrorDomainDescription.networkRequestDomain.rawValue, code: ErrorDomainCode.unexpectedResponseFromAPI.rawValue, userInfo: [NSLocalizedDescriptionKey: errorString])
completion(.failure(error))
}
}
func returnedProductedArray() -> [ProductDTO] {
let product1 = ProductDTO(idFromBackend: "product-1", name: "product-1", description: "product-description", price: 3.55, photo: nil)
let product2 = ProductDTO(idFromBackend: "product-2", name: "product-2", description: "product-description-2", price: 5.55, photo: nil)
let product3 = ProductDTO(idFromBackend: "product-3", name: "product-3", description: "product-description-3", price: 8.55, photo: nil)
return [product1, product2, product3]
}
}