Rxswift: ControlProperty for UISlider.value in unit tests not emitting .onNext

Created on 7 Aug 2017  路  11Comments  路  Source: ReactiveX/RxSwift

Short description of the issue:

When running a test around UISlider, changing the value of the slider does not emit a new signal with the new value.

Expected outcome:

onNext to emit the new value and the test to pass

What actually happens:

onNext does not emit the new value

Self contained code example that reproduces the issue:

          let slider = UISlider()
          var outValue: Float = 0
          slider.rx.value.subscribe(onNext: { value in
            outValue = value
          }).disposed(by: disposeBag)
          slider.value = 0.3
          expect(outValue).toEventually(equal(0.3))

RxSwift/RxCocoa/RxBlocking/RxTest version/commit

3.6.1

Platform/Environment

  • [x] iOS
  • [ ] macOS
  • [ ] tvOS
  • [ ] watchOS
  • [ ] playgrounds

How easy is to reproduce? (chances of successful reproduce after running the self contained code)

  • [x] easy, 100% repro
  • [ ] sometimes, 10%-100%
  • [ ] hard, 2% - 10%
  • [ ] extremely hard, %0 - 2%

Xcode version:

8.3.2

Installation method:

  • [ ] CocoaPods
  • [x] Carthage
  • [ ] Git submodules

I have multiple versions of Xcode installed:
(so we can know if this is a potential cause of your issue)

  • [ ] yes (which ones)
  • [x] no

Level of RxSwift knowledge:
(this is so we can understand your level of knowledge
and formulate the response in an appropriate manner)

  • [ ] just starting
  • [x] I have a small code base
  • [ ] I have a significant code base

Most helpful comment

Even though manual invocation hack is possible, don't do it because:

  • it can break easily in future, there are no guarantees we'll use the same observing APIs
  • it goes against apple guidelines
  • it's using Apple's APIs in non intended way
  • it's conceptually bad, there are reasons why Apple has decided their APIs work this way, and why we've decided to honor their decisions

All 11 comments

Hi, @jsetting32 ControlProperty does not emit value on programatic changes, only for a user input.

You can force it tho by calling setAction(:, forControlEvents:)

What would setAction(:, forControlEvents:) be invoked by? UISlider? Doesn't look like UISlider has such a method.

Every UIControl has that method.
The point is a Control Event is described as a user initiated action like interacting with the slider or using the keyboard to change a text field.

Programmatically setting the values isn't considered a Control Event in that sense, so that manual invocation is needed

Even though manual invocation hack is possible, don't do it because:

  • it can break easily in future, there are no guarantees we'll use the same observing APIs
  • it goes against apple guidelines
  • it's using Apple's APIs in non intended way
  • it's conceptually bad, there are reasons why Apple has decided their APIs work this way, and why we've decided to honor their decisions

I didn't see setAction(:, forControlEvents:). You meant slider.sendActions(for: UIControlEvents.valueChanged) correct?

Oh, sorry there was a typo, yeah slider.sendActions(for: UIControlEvents.valueChanged)

What would be a good alternative @kzaher? Or just forget the test and assume it works correctly? Was attempting to get a good amount of coverage is why I wanted to manually invoke a control event

@jsetting32
I agree with @kzaher generally speaking.

For example, if the UISlider is driving some PublishSubject in your ViewModel, it would be more proper to test the ViewModel directly and not the UI driving the ViewModel.

In general it makes more sense to test Data and not UI Components.

makes sense @freak4pc 馃憤

Also, I attempted to invoke a user action, but still nothing

          let slider = UISlider()
          var outValue: Float = 0
          slider.rx.value.subscribe(onNext: { value in
            outValue = value
          }).disposed(by: disposeBag)
          slider.value = 0.3
          slider.sendActions(for: UIControlEvents.valueChanged)
          expect(outValue) == 0.3
          expect(outValue).toEventually(equal(0.3))

@jsetting32 As far as I can tell this code snippet works in real app and unit tests. Try putting a breakpoint inside onNext callback.

Again, you shouldn't be using controls inside unit tests unless you are working for Apple and testing UIKit controls.

This isn't a library issue IMHO.

Was this page helpful?
0 / 5 - 0 ratings