-1

I'm writing unit test for my codes which using Combine framework

i have a async operation, so i use expectation(descrption:) to wait async operation

this is my code example

class MyViewModel {
  private var someOtherSubject: CurrentValueSubject<...>(...)
  var someOtherStream: ... { 
    return someOtherSubject.eraseToAnyPublisher()
  }
  
  init(...) { 
    .
    .
    bind()
  }

  private func bind() { 
    . 
    .
    .
    someOfMySubject
      .flatMap { someAsyncOperation }
      .sink { [weak self] ... in 
         self?.someOtherSubject.send(value2)
      }
      .store(..)
  }

  .
  .

  func handleSomeUserAction() {
    self.someOtherSubject.send(value1)
    self.someOfMySubject.send(someEvent)
  }
}

i'm trying to check value which emitted by someOtherStream when handleSomeUserAction is called

so i wrote test code like this

func test_handleSomeUserAction() { 
  // given 
  let expectation = expectation(description: "...")
  var result: ValueType?

  // when 
  sut.someOtherStream
    .dropFirst() // drop CurretValueSubject's default value
    .sink { 
      result = $0
      expectation.fulfill()
    }
    .store(...)
  sut.handleSomeUserAction()

  // then
  wait(for: [expectation], timeout: 1.0)
  let unwrapped = try XCTUnwrap(result)
  XCTAssertEqual(unwrapped, value1)
}

i know expectedFulfillmentCount is 1 because it's default value is 1

but test is failed and Xcode show me this message

API violation - multiple calls made to -[XCTestExpectation fulfill]

so i tried to debug, and i figured out expectation.fulfill() called twice despite of expectedFulfillmentCount is 1

and result is value2 not value1 that i expected

why this happend? i think async operation(flatMap {...}) is finished too fast and fulfill called twice.

i tried delay mock method but i think this is not the right solution

PrepareFor
  • 2,448
  • 6
  • 22
  • 36
  • Please edit your question to include an appropriate [mcve]. There is a lot of omitted code in your current question. You don't even show where you call `fulfill` – Paulw11 Oct 19 '21 at 20:07
  • @Paulw11 I added fulfill in my code. In that situation, if aync operation quit too fast, can fulfill method called twice – PrepareFor Oct 19 '21 at 23:07
  • 1
    It's nothing to do with speed. Your subscriber chain is being called twice. Because you have a `CurrentValueSubject`. It will fire with the initial value and then fire when you update the value. – Paulw11 Oct 20 '21 at 00:28
  • @Paulw11 so i dropped first element using dropFirst operator to get value that i want to receive – PrepareFor Oct 20 '21 at 00:51
  • Use the debugger. Set breakpoints or print the values you are receiving. The code in your question is too complex and too reduced to follow what is happening. Create a [mcve]. The chances are that in doing so you may well solve the problem – Paulw11 Oct 20 '21 at 01:25

1 Answers1

2

You code is kind of strange. But it looks like you are sending two values to someOtherStream. So it is expected that the sink is called twice.

dasdom
  • 13,975
  • 2
  • 47
  • 58