uber / mockolo

Efficient Mock Generator for Swift
Apache License 2.0
824 stars 87 forks source link

Fail to generate mock with protocol inheritance #146

Closed keithcml closed 3 years ago

keithcml commented 3 years ago

Our team is also using RIBs as our app architecture. When using Mockolo to generate mock for RootInteractable, the parent protocol methods, Interactable and InteractorScope, cannot be generated.

Example:

protocol RootInteractable: Interactable {
    var router: RootRouting? { get set }

    func someMethod()
}

Generated codes:

class RootInteractableMock: RootInteractable {
    init() { }
    init(router: RootRouting? = nil) {
        self.router = router
    }

    private(set) var routerSetCallCount = 0
    var router: RootRouting? = nil { didSet { routerSetCallCount += 1 } }

    private(set) var someMethodCount = 0
    func someMethod()  {
        someMethodCount += 1        
    }
}

image

I am using the latest downloaded binary and Xcode 12.4.

I think the problem is Interactor is the protocol in RIBs module. And Mockolo seems cannot mock from external module.

keithcml commented 3 years ago
image

I can do the mock on the class of RootInteractor instead of the protocol.

But in this way, the RootInteractor class cannot mark the final keyword on it.

EspressoCup commented 3 years ago

If RootInteractable and Interactable are in two different modules, they both need to be annotated with /// @mockable.

keithcml commented 3 years ago

@EspressoCup That means I need to folk the RIBs repo and update for our own use? Is there any way that framework consumers can annotate instead of waiting for module maker to do it. And seems it is not practical for framework maker to annotate for their consumers.

EspressoCup commented 3 years ago

I should've asked this question first: are you building mocks per module (run mockolo for each target) or for the entire app (run mockolo once for your entire codebase)? My previous comment applies to the former. If the latter, only the child protocol needs to be annotated.

If the former, you have the following options.

  1. Fork and add annotations yourself as you mentioned.
  2. Declare all of the members of the parent protocol in your child protocol (where you annotate /// @mockable).
  3. Create a manual mock class for each parent protocol and pass the mock files in as 'processed' files in the mock generator.

The option 2 is probably most straightforward. Lmk if that helps.

keithcml commented 3 years ago

My situation is, I have 2 frameworks, A depends on B. I have a protocol in B (aka ProtocolB).

/// @mockable
protocol ProtocolB {
  func thisIsB()
}

In Framework A, I have also had a protocol, inherit ProtocolB.

/// @mockable
protocol ProtocolA: ProtocolB {
  func thisIsA()
}

Then I write unit tests on Framework A. Only ProtocolA methods generated for ProtocolAMock.

class ProtocolAMock: ProtocolA {
  func thisIsA() {}

  // missing thisIsB() and there is compile error.
}

Is it the expected behavior? @EspressoCup

EspressoCup commented 3 years ago

@keithcml Are you building mocks per module (run 'mockolo' for each target) or for the entire app (run 'mockolo' once for your entire codebase)?

Feel free to reopen this if the issues persist.

keithcml commented 3 years ago

@EspressoCup I want to try

Create a manual mock class for each parent protocol and pass the mock files in as 'processed' files in the mock generator.

But may I have step by step guide for this? Seems I should use -mock-filelist options. But how is the file content.