writing your individual structure and perceive its significance (half 3) – Suppose And Construct


It the earlier two articles we noticed how one can setup and implement VIPER-S. Within the third and final of the sequence we will probably be specializing in sharing data between modules and testing.

Sharing information between modules

Passing data between modules is an important activity, and our structure ought to have the ability to deal with it in a transparent approach.
The primary move we’ll focus on is required when a module must ship data to the next module, like after we choose an merchandise from the ItemsList. On this case we wish to present the ItemsDetails module for the chosen merchandise.

The answer that I’ve carried out for VIPER-S resides within the Navigator of the ItemsDetails module. The ItemsDetails module doesn’t imply something with out an Merchandise: it wants an Merchandise reference to work appropriately. That being mentioned, it’s a good suggestion to construct the module by passing the Merchandise on to its makeModule() perform, and storing the Merchandise reference within the module director in order that it’s accessible for all the opposite actors. Right here is the perform code:


  
static func makeModule(with merchandise:Merchandise)-> ItemsDetailsScene {

    // Crete the actors


    director.ui = scene
    director.merchandise = merchandise // HERE the merchandise is saved inside the director
    scene.eventsHandler = director

    return scene
}

and we are able to name it from the ItemsList module director, when the person selects an merchandise.

 

    func onItemSelected(index:Int) {
        let merchandise = gadgets[index]
        navigator.gotoDetail(for: merchandise)
    }

One other actually widespread case the place we have to move data from one module to a different, is when a module needs to be dismissed after performing an operation that may alter the knowledge of the presenting (earlier) module. In that case the earlier module needs to be up to date earlier than being proven once more. In our instance app we encounter this move when a brand new merchandise is created by the ItemsAdd module. After the merchandise has been created we wish to replace the ItemsList.

This is the flow of this map

We are able to use delegates or notification/observers as standard right here. On this case I’ve carried out my answer utilizing delegates. The delegate object will implement the “ItemsAdd_Delegate” protocol that requires the “itemsDidCreate” perform to be carried out. For the reason that move is kind of widespread, I believe that, once more, it’s a good suggestion to construct the ItemsAdd module by passing the delegate with the makeModule perform straight and storing it with the director.

    
static func makeModule(with delegate:ItemsAdd_Delegate?)-> ItemsAddScene {
        
        // Create the actors
        
        let scene = instantiateController(id: "Add", storyboard: "Objects", bundle:Bundle(for: self)) as! ItemsAddScene
        let navigator = ItemsAddNavigator(with: scene)
        let employee = ItemsAddWorker()
        let director = ItemsAddDirector()
        
        // Affiliate actors
        
        director.dataManager = employee
        director.navigator = navigator
        director.ui = scene
        director.delegate = delegate // PASS the delegate to the director
        employee.presenter = director
        scene.eventsHandler = director
        
        return scene
}

So you’ll be able to simply current the ItemsAdd module from the ItemsList module like this:

 

    // ItemsAddDirector code 
    func onAddItem() {
        navigator.gotoAddItem(with: self)
    }

    // ItemsAddNavigator code 
    func gotoAddItem(with delegate:ItemsAdd_Delegate?) {
        let itemAdd = ItemsAddNavigator.makeModule(with: delegate)
        push(nextViewController: itemAdd)
    }

When the ItemsAdd module completes its operation, it calls the perform itemDidCreate on the delegate that at this level can simply carry out any wanted replace on the view.

extension ItemsAddDirector: ItemsAdd_PresentData{
    
    func presentSuccess(){
        delegate?.itemDidCreate()
        navigator.goBack()
    }

Whereas there are actually quite a lot of circumstances we haven’t coated, I’m assured that we are able to simply deal with them through the use of the options we simply checked out as a reference.

Testing the structure

It’s now time to speak about testing, a subject actually near our hearts!
Testing the code that we now have written till now could be very easy however I have to admit I nonetheless have doubts about how one can take a look at some actors… Let’s begin by checking checks that may be very simply built-in in our code.

The structure is pushed by protocols so it’s fairly straightforward to write down spy objects that we are able to inject inside the category we’re testing to maintain observe of its behaviour.

Let’s begin by testing the director of the ItemsList module. This class implements 2 protocols, “ItemsList_HandleUIEvents” and “ItemsList_PresentData”, that in flip are strictly associated to UI and dataManager objects of the director class. Let’s examine the code for the “onUIReady” perform carried out within the director class to obviously perceive what I imply right here:

    func onUIReady() {
        ui.toggle(loading: true)
        dataManager.fetchItems()
    }

The UI and dataManager objects are simply an implementation of two different protocols: “ItemsList_DisplayUI” and “ItemsList_ManageData”.

WRITING SPIES

A spy is a “faux” implementation of a protocol that retains observe of all the knowledge that move by means of it and it’s the approach I’m utilizing to check all of the VIPER-S modules. Relying on how deeply you wish to take a look at your courses you’ll be able to write spies that tracks kind of data throughout checks execution. For the reason that code written since now could be fairly easy we’ll cowl all of the features calls with all of the obtained params.

The dataManager object of the director implements “ItemsList_ManageData”. Right here is the code for its spy:

    class DataManagerSpy: ItemsList_ManageData{
        
        var fetched: Bool = false
        var deleted: Bool = false

        func fetchItems(){ fetched = true }
        func deleteAllItems(){ deleted = true }
    }

Straightforward. It simply makes use of two variables to outline if any features has been referred to as.

Let’s examine the UI spy now, an implementation of “ItemsList_DisplayUI” protocol.

    class UISpy: ItemsList_DisplayUI{
        
        var isLoading:Bool = false
        var presentedItems:[ItemUI] = []
        var errorIsPresented:Bool = false
        var successIsPresented:Bool = false
        
        func toggle(loading:Bool){ isLoading = loading }
        func show(gadgets: [ItemUI]){ presentedItems = gadgets }
        func displayError(){ errorIsPresented = true }
        func displaySuccess(){ successIsPresented = true }
    }

The “toggle(loading:)” perform shops the state of the loader inside the “isLoading” properties, whereas “presentedItems” retains observe of the gadgets obtained by the “show(gadgets:)” perform. “displayError” and “displaySuccess” calls are simply tracked by two booleans.

One other spy that we wish to inject is devoted to the navigator property, simply to ensure that the director is implementing the anticipated app move.

    class NavigatorSpy: ItemsList_Navigate{
        
        var selectedItem: Merchandise? = nil
        var goingToAddItem: Bool = false
        var goingBack: Bool = false
        
        func gotoDetail(`for` merchandise:Merchandise){ selectedItem = merchandise }
        func gotoAddItem(with delegate:ItemsAdd_Delegate?) 
             { goingToAddItem = true }
        func goBack(){ goingBack = true }
    }

Right here we maintain a reference to the merchandise that will probably be handed to the “gotoDetail” perform with the “selectedItem” property and we use booleans to confirm that the opposite features have been referred to as.

The code for these courses has been written inside the category of the director take a look at (check out the “ItemsListDirectorTest.swift” code), in order that it is going to be effective to make use of the identical names for the spies of the opposite actors getting a a lot cleaner, readable and predictable code.

INJECTING THE SPIES

The fitting place to inject and reset the spies is contained in the take a look at “setUp” perform. This perform will probably be referred to as earlier than every take a look at is executed, resetting the state for the spies. We must also maintain references to the spies objects to have the ability to entry them from our checks. Right here is the code used to carry out the setup and injection of the spies:

class ItemsListDirectorTests: XCTestCase {
    ……
    right here we’ve  carried out the spies courses
    ……
   
    // MARK: Setup checks 

    var director: ItemsListDirector!
    var navigator: NavigatorSpy!
    var dataManager: DataManagerSpy!
    var ui: UISpy!
    
    override func setUp() {
        tremendous.setUp()
        
        director = ItemsListDirector()
        ui = UISpy()
        navigator = NavigatorSpy()
        dataManager = DataManagerSpy()
        
        director.ui = ui
        director.navigator = navigator
        director.dataManager = dataManager
    }


As you’ll be able to see we’re writing one thing just like the code written contained in the makeModule perform of the navigator, injecting our model of UI, information supervisor and navigator. Now we are able to simply take a look at all of the director features.

WRITING CODE FOR TESTS

The onUIReady perform is very easy to check at this level. Let’s examine its code once more:

    func onUIReady() {
        ui.toggle(loading: true)
        dataManager.fetchItems()
    }

We all know that it has to run the loader on the UI and fetch gadgets on the dataManager. Let’s take a look at this move through the use of our spies. We have to confirm the property “isLoading” to be true for the “UISpy” occasion, and the property “fetched” to be true for the “DataManagerSpy” occasion. Nothing else.

    func test_onUIReady(){
        
        // When
        director.onUIReady()
        
        // Then 
        XCTAssertTrue(ui.isLoading)
        XCTAssertTrue(dataManager.fetched)
    }

We are able to simply receive a semantic separation of the code through the use of easy feedback that make the code much more readable:
//Given: to outline some particular circumstances for the take a look at (not wanted for the onUIReady perform)
//When: to outline the principle motion that we’re testing
//Then: the place we write the expectation for the take a look at

We are able to learn the code like a sentence: GIVEN these circumstances, WHEN we execute this motion, THEN we anticipate these outcomes.

Let’s see a extra fascinating take a look at to show that director is ready to current gadgets. On this case we wish to take a look at the “presentItems” perform. That is the unique code written within the director class for that perform:

    func current(gadgets:[Item]){
        self.gadgets = gadgets
        ui.show(gadgets: itemsUI)
        ui.toggle(loading: false)   
    }

The record of things is saved within the director, the gadgets are introduced, and the loader is stopped.

let’s examine now how we are able to take a look at this behaviour:

    func test_presentItems(){
        
        // Given
        let item_one = Merchandise(title: "one", enabled: false, date: Date())
        let item_two = Merchandise(title: "one", enabled: false, date: Date())
        ui.isLoading = true

        // When
        director.current(gadgets: [item_one, item_two])
        
        // Then
        XCTAssertFalse(ui.isLoading)
        XCTAssertTrue(director.gadgets.rely == 2)
        XCTAssertTrue(ui.presentedItems.rely == 2)
    }

The circumstances listed here are that we’re presenting “item_one” and “item_two” and that the view is at the moment loading. So we initialize the merchandise presentation within the “when” block and we anticipate the UI to not be loading, the whole variety of gadgets saved within the director to be 2, and the introduced gadgets within the UI (the spy) to once more be 2. We might additionally write a extra particular take a look at to examine that the gadgets introduced are precisely the identical as these we now have obtained. I’d wish to stress the truth that we’re utilizing injected code to confirm the unique move of the director right here. Now right here’s a portion of the code for the UISpy we now have beforehand introduced:

    class UISpy: ItemsList_DisplayUI{
        ….. .        
        var presentedItems:[ItemUI] = []
        ……         
        func show(gadgets: [ItemUI]){ presentedItems = gadgets }
       …… 
    }

The director will name the injected “show(gadgets:)” on the injected UI that solely has the position of storing the gadgets in an area variable. What we’re testing right here is that the gadgets handed to “director.current(gadgets:)” arrive to the UI (the faux one we’re utilizing for the take a look at).

TESTING THE WORKER

Equally to how we did it for the director, we are able to inject spies on the employee. This time we have to inject a presenter (ItemsList_presentData), however we must also discover a strategy to simulate calls to the “NetworkManager”.

When we have to work together with different objects which might be out of the scope of the testing (or which might be already examined in a special context), it’s a widespread follow to substitute the article with a dummy of the unique object. In our case the NetworkManager is an opaque object and we don’t want to check it. We are able to simply substitute the file imported within the take a look at goal with a very totally different file that has the identical class title, features and properties of the “unique” one. Alternatively we are able to stub the community calls utilizing libraries like “OHHTTPStubs”. Really, contemplating our community supervisor doesn’t carry out any actual community name (and because it’s actually easy) it’s okay to observe the primary methodology and create a faux model included just for the take a look at goal. With this implementation we add some helpful variables that outline how the community supervisor replies to a name. Particularly, we now have the storedItems property that shops the gadgets that we wish to return after the getItems name and a boolean “nextCallWillFail” which determines if we’re simulating a name that fails.

    static var storedItems:[Item] = []
    static var nextCallWillFail: Bool = false

We now have already introduces the spies logic, so I’m simply going to indicate you all the code that implements and injects the presenter spy for the employee:

class ItemsListWorkerTests: XCTestCase {
    
    // MARK: - Spies
    
    class PresenterSpy:ItemsList_PresentData {
        
        var presentedItems:[Item] = []
        var isSuccessPresented = false
        var isErrorPresented = false
        var expectation:XCTestExpectation? = nil
        
        func current(gadgets:[Item]){ 
                presentedItems = gadgets; expectation?.fulfill(); }
        func presentSuccess(){ isSuccessPresented = true }
        func presentError(){ isErrorPresented = true }
    }
    
    // MARK: - Take a look at setup
    
    var employee: ItemsListWorker!
    var presenter: PresenterSpy!
    
    override func setUp() {
        tremendous.setUp()
        
        employee = ItemsListWorker()
        presenter = PresenterSpy()
        employee.presenter = presenter
    }

With this code in thoughts we are able to implement checks for the fetchItems perform. We wish to take a look at for each failing and succeeding calls, so let’s begin by analyzing the previous case:

    
    func test_fetchItemsCompleted(){
        
        // Given 
        let item_one = Merchandise(title: "one", enabled: true, date: Date() )
        let item_two = Merchandise(title: "two", enabled: false, date: Date())
        
        NetworkManager.storedItems = [item_one, item_two]
        NetworkManager.nextCallWillFail = false
        
        let anticipate = expectation(description: "fetch")
        presenter.expectation = anticipate
        
        // When
        employee.fetchItems()
        
        // Then
        wait(for: [expect], timeout: 1)
        XCTAssertTrue(presenter.presentedItems.rely == 2)
        
    }
    

The given circumstances are for our model of “NetworkManager” to return 2 gadgets and for the community name to not fail. When the employee calls the fetchItems perform we anticipate to see the two gadgets that we now have beforehand injected handed to the presenter. An expectation places the take a look at perform on maintain and it is going to be fulfilled with the injected model of “current(gadgets:)” within the presenter spy.

Equally, we are able to take a look at the community name failure by setting nextCallWillFail to true.

On this case we don’t anticipate components to be handed to the presenter and we’ll simply examine if the error has been introduced.

    func test_fetchItemsFailed(){
        
        // Given
        let item_one = Merchandise(title: "one", enabled: true, date: Date() )
        let item_two = Merchandise(title: "two", enabled: false, date: Date())
        
        NetworkManager.storedItems = [item_one, item_two]
        NetworkManager.nextCallWillFail = true
        
        // When
        employee.fetchItems()
        
        // Then
        XCTAssertTrue(presenter.presentedItems.rely == 0)
        XCTAssertTrue(presenter.isErrorPresented)
    }
    

TESTING THE NAVIGATOR

In the meanwhile the one factor that we take a look at for the navigator is the “makeModule” perform. We wish to ensure that the structure is revered and that each one the actors have been created and assigned to the fitting objects.

Right here is the code for the navigator take a look at suite:

class ItemsListNavigatorTests: XCTestCase {

    func test_makeModule(){
    
        // Given 
        let module = ItemsListNavigator.makeModule()
        
        // Then
        guard let director = module.eventsHandler as? ItemsListDirector else{
            XCTFail("No director outlined")
            return
        }
        
        guard let employee = director.dataManager as? ItemsListWorker else{
            XCTFail("No employee outlined")
            return
        }
        
        guard let _ = director.navigator as? ItemsListNavigator else{
            XCTFail("No navigator outlined")
            return
        }
        
        guard let _ = director.ui as? ItemsListScene else{
            XCTFail("no scene outlined")
            return
        }
        
        guard let _ = employee.presenter as? ItemsListDirector else{
            XCTFail("no presenter outlined")
            return
        }   
    }
}

Nothing loopy, proper. We simply be sure that the code outlined by the makeModule perform is producing the anticipated structure with the fitting actors in place.

You could find all of the checks for the opposite actors on the github venture. Please observe that I’m not testing the scene in any respect… be at liberty to counsel how you’d take a look at it!

Conclusions

VIPER-S is way away to be good, it has professionals (very nicely organized and readable code) and cons (numerous boilerplate code and too many information), however above anything it was an ideal studying expertise and I hope you will have appreciated it too, if solely from a purely theoretical perspective. As I advised you originally, there are nonetheless uncertainties and important components that must be addressed, so it could be tremendous cool should you wished to share your viewpoint with me (on Twitter)!

Final however not least, high-fives to Nicola, who once more took time to evaluation the article! [NA: Back at you. Take care y’all!]





Source_link

Leave a Reply

Your email address will not be published.