Skip to content

Implement tests - Part 3 #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: part2-di
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Clean, Simple and Composable Routing for iOS Apps

This is the [second part](https://cassiuspacheco.com/applying-dependency-injection-to-composable-routing-for-ios-apps-ck7v10xaz01h9zis1oksoe7h0) of a series of blog posts about [Clean, Simple and Composable Routing for iOS Apps](https://hashnode.com/series/clean-simple-and-composable-routing-for-ios-apps-ck7vm42k401n4zis1wu4ar2od).
This is the [third part](https://cassiuspacheco.com/unit-testing-composable-routing-in-swift-for-ios-apps-part-3-ck85u05av0018shs1vdc64t23) of a series of blog posts about [Clean, Simple and Composable Routing for iOS Apps](https://hashnode.com/series/clean-simple-and-composable-routing-for-ios-apps-ck7vm42k401n4zis1wu4ar2od).

## App's Flow Diagram

Expand Down
196 changes: 192 additions & 4 deletions RoutingExample.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
Expand Down
144 changes: 144 additions & 0 deletions RoutingExampleTests/Routers Tests/DeeplinkRouterTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//
// DeeplinkRouterTests.swift
// RoutingExampleTests
//
// Created by Cassius Pacheco on 14/3/20.
// Copyright © 2020 Cassius Pacheco. All rights reserved.
//

import XCTest
import DependencyContainer
@testable import RoutingExample

final class DeeplinkRouterTests: XCTestCase {
let root = ViewControllerMock()

// MARK: - AppRoutes tests

func testProductRoute() {
XCTAssertEqual(AppRoutes(url: URL(string: "www.routing.com/product?something=meh")!), AppRoutes.product)
}

func testLoginRoute() {
XCTAssertEqual(AppRoutes(url: URL(string: "www.routing.com/login?something=meh")!), AppRoutes.login)
}

func testSignUpRoute() {
XCTAssertEqual(AppRoutes(url: URL(string: "www.routing.com/sign-up?something=meh")!), AppRoutes.signUp)
}

func testWishlistUpRoute() {
XCTAssertEqual(AppRoutes(url: URL(string: "www.routing.com/wishlist?something=meh")!), AppRoutes.wishlist)
}

func testShopUpRoute() {
XCTAssertEqual(AppRoutes(url: URL(string: "www.routing.com/shop?something=meh")!), AppRoutes.shop)
}

func testInvalidRoute() {
XCTAssertNil(AppRoutes(url: URL(string: "www.routing.com")!))
}
}

extension DeeplinkRouterTests {
// MARK: - Route to url tests

func testProductDeeplinkRoute() {
let transition = TransitionMock()
let router = DeeplinkRouter.makeDeeplink()
router.root = root

let result = router.route(to: URL(string: "www.routing.com/product?something=meh")!, as: transition)

XCTAssertTrue(result)
XCTAssertEqual(transition.openViewControllerCallsCount, 1)
XCTAssertTrue(transition.openViewControllerReceivedVC is ProductViewController)
XCTAssertTrue(transition.openViewControllerReceivedFromVC === root)
XCTAssertNil(transition.openViewControllerReceivedCompletion)
}

func testLoginDeeplinkRoute() {
let transition = TransitionMock()
let router = DeeplinkRouter.makeDeeplink()
router.root = root

let result = router.route(to: URL(string: "www.routing.com/login?something=meh")!, as: transition)

XCTAssertTrue(result)
XCTAssertEqual(transition.openViewControllerCallsCount, 1)
XCTAssertTrue(transition.openViewControllerReceivedVC?.asNavigationsRootVC() is LoginViewController)
XCTAssertTrue(transition.openViewControllerReceivedFromVC === root)
XCTAssertNil(transition.openViewControllerReceivedCompletion)
}

func testSignUpDeeplinkRoute() {
let transition = TransitionMock()
let router = DeeplinkRouter.makeDeeplink()
router.root = root

let result = router.route(to: URL(string: "www.routing.com/sign-up?something=meh")!, as: transition)

XCTAssertTrue(result)
XCTAssertEqual(transition.openViewControllerCallsCount, 1)
XCTAssertTrue(transition.openViewControllerReceivedVC is SignUpViewController)
XCTAssertTrue(transition.openViewControllerReceivedFromVC === root)
XCTAssertNil(transition.openViewControllerReceivedCompletion)
}

func testWishlistDeeplinkRoute() {
let tabbar = UITabBarController()
tabbar.viewControllers = [UIViewController(), UIViewController(), UIViewController()]
root.underlyingTabBarController = tabbar
tabbar.selectedIndex = 2

let transition = TransitionMock()
let router = DeeplinkRouter.makeDeeplink()
router.root = root

let result = router.route(to: URL(string: "www.routing.com/wishlist?something=meh")!, as: transition)

XCTAssertTrue(result)
XCTAssertEqual(transition.openViewControllerCallsCount, 0, "Nothing was opened")
XCTAssertEqual(root.tabBarController?.selectedIndex, 1, "Wishlist is the index 1")
}

func testShopDeeplinkRoute() {
let tabbar = UITabBarController()
tabbar.viewControllers = [UIViewController(), UIViewController(), UIViewController()]
root.underlyingTabBarController = tabbar
tabbar.selectedIndex = 2

let transition = TransitionMock()
let router = DeeplinkRouter.makeDeeplink()
router.root = root

let result = router.route(to: URL(string: "www.routing.com/shop?something=meh")!, as: transition)

XCTAssertTrue(result)
XCTAssertEqual(transition.openViewControllerCallsCount, 0, "Nothing was opened")
XCTAssertEqual(root.tabBarController?.selectedIndex, 0, "Shop is the index 0")
}

func testInvalidDeeplinkRoute() {
let tabbar = UITabBarController()
tabbar.viewControllers = [UIViewController(), UIViewController(), UIViewController()]
root.underlyingTabBarController = tabbar
tabbar.selectedIndex = 2

let transition = TransitionMock()
let router = DeeplinkRouter.makeDeeplink()
router.root = root

let result = router.route(to: URL(string: "www.routing.com")!, as: transition)

XCTAssertFalse(result)
}
}

extension DeeplinkRouter {
/// Helper method that injects mocked value by default.
static func makeDeeplink(rootTransition: Transition = TransitionMock(),
container: DependencyContainer = DependencyContainer.mock()) -> DeeplinkRouter {
return DeeplinkRouter(rootTransition: rootTransition, container: container)
}
}
174 changes: 174 additions & 0 deletions RoutingExampleTests/Routers Tests/DefaultRouterTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
//
// DefaultRouterTests.swift
// RoutingExampleTests
//
// Created by Cassius Pacheco on 14/3/20.
// Copyright © 2020 Cassius Pacheco. All rights reserved.
//

import XCTest
import DependencyContainer
@testable import RoutingExample

final class DefaultRouterTests: XCTestCase {
let root = ViewControllerMock()

// MARK: - Open methods

func testRouteToNotOpeningAnythingWhenRootIsNil() {
let transition = TransitionMock()
let router = DefaultRouter.makeDefault()

router.route(to: UIViewController(), as: transition)

XCTAssertNil(router.root)
XCTAssertEqual(transition.openViewControllerCallsCount, 0, "Nothing is opened if root is nil")
}

func testRouteToOpeningControllerWhenRootIsNotNil() {
let transition = TransitionMock()
let router = DefaultRouter.makeDefault()
router.root = root

let toBePresented = UIViewController()
router.route(to: toBePresented, as: transition)

XCTAssertNotNil(router.root)
XCTAssertEqual(transition.openViewControllerCallsCount, 1)
XCTAssertTrue(transition.openViewControllerReceivedVC === toBePresented)
XCTAssertTrue(transition.openViewControllerReceivedFromVC === root)
}

func testRouteToCompletionNotOpeningAnythingWhenRootIsNil() {
let transition = TransitionMock()
let router = DefaultRouter.makeDefault()

router.route(to: UIViewController(), as: transition, completion: { print("yay") })

XCTAssertNil(router.root)
XCTAssertEqual(transition.openViewControllerCallsCount, 0, "Nothing is opened if root is nil")
}

func testRouteToCompletionOpeningControllerWhenRootIsNotNil() {
let transition = TransitionMock()
let router = DefaultRouter.makeDefault()
router.root = root


let toBePresented = UIViewController()
router.route(to: toBePresented, as: transition, completion: { print("yay") })

XCTAssertNotNil(router.root)
XCTAssertEqual(transition.openViewControllerCallsCount, 1)
XCTAssertTrue(transition.openViewControllerReceivedVC === toBePresented)
XCTAssertTrue(transition.openViewControllerReceivedFromVC === root)
XCTAssertNotNil(transition.openViewControllerReceivedCompletion)
}
}

extension DefaultRouterTests {
// MARK: - Close methods

func testCloseNotClosingAnythingWhenRootIsNil() {
let transition = TransitionMock()
let router = DefaultRouter.makeDefault(rootTransition: transition)

router.close()

XCTAssertNil(router.root)
XCTAssertEqual(transition.closeViewControllerCallsCount, 0, "Nothing was closed if root is nil")
}

func testCloseClosesControllerWhenRootIsNotNil() {
let transition = TransitionMock()
let router = DefaultRouter.makeDefault(rootTransition: transition)
router.root = root

router.close()

XCTAssertNotNil(router.root)
XCTAssertEqual(transition.closeViewControllerCallsCount, 1)
XCTAssertTrue(transition.closeViewControllerReceivedVC === root)
}

func testCloseCompletionNotClosingAnythingWhenRootIsNil() {
let transition = TransitionMock()
let router = DefaultRouter.makeDefault(rootTransition: transition)

router.close(completion: { print("yay") })

XCTAssertNil(router.root)
XCTAssertEqual(transition.closeViewControllerCallsCount, 0, "Nothing was closed if root is nil")
}

func testCloseCompletionClosingControllerWhenRootIsNotNil() {
let transition = TransitionMock()
let router = DefaultRouter.makeDefault(rootTransition: transition)
router.root = root

router.close(completion: { print("yay") })

XCTAssertNotNil(router.root)
XCTAssertEqual(transition.closeViewControllerCallsCount, 1)
XCTAssertTrue(transition.closeViewControllerReceivedVC === root)
XCTAssertNotNil(transition.closeViewControllerReceivedCompletion)
}
}

extension DefaultRouterTests {
// MARK: - Dismiss methods

func testDismissNotDismissingAnythingWhenRootIsNil() {
let transition = TransitionMock()
let router = DefaultRouter.makeDefault(rootTransition: transition)

router.dismiss()

XCTAssertNil(router.root)
XCTAssertEqual(root.dismissWithAnimatedCalledCount, 0, "Nothing was closed if root is nil")
}

func testDismissClosesControllerWhenRootIsNotNil() {
let transition = TransitionMock()
let router = DefaultRouter.makeDefault(rootTransition: transition)
router.root = root

router.dismiss()

XCTAssertNotNil(router.root)
XCTAssertEqual(root.dismissWithAnimatedCalledCount, 1, "Dismiss calls the root's dismiss directly")
XCTAssertEqual(root.dismissWithAnimatedArguments?.animated, transition.isAnimated)
XCTAssertNil(root.dismissWithAnimatedArguments?.completion)
}

func testDismissCompletionNotDismissingAnythingWhenRootIsNil() {
let transition = TransitionMock()
let router = DefaultRouter.makeDefault(rootTransition: transition)

router.dismiss(completion: { print("yay") })

XCTAssertNil(router.root)
XCTAssertEqual(root.dismissWithAnimatedCalledCount, 0, "Nothing was closed if root is nil")
}

func testDismissCompletionDismissingControllerWhenRootIsNotNil() {
let transition = TransitionMock()
let router = DefaultRouter.makeDefault(rootTransition: transition)
router.root = root

router.dismiss(completion: { print("yay") })

XCTAssertNotNil(router.root)
XCTAssertEqual(root.dismissWithAnimatedCalledCount, 1, "Dismiss calls the root's dismiss directly")
XCTAssertEqual(root.dismissWithAnimatedArguments?.animated, transition.isAnimated)
XCTAssertNotNil(root.dismissWithAnimatedArguments?.completion)
}
}

extension DefaultRouter {
/// Helper method that injects mocked value by default.
static func makeDefault(rootTransition: Transition = TransitionMock(),
container: DependencyContainer = DependencyContainer.mock()) -> DefaultRouter {
return DefaultRouter(rootTransition: rootTransition, container: container)
}
}
21 changes: 21 additions & 0 deletions RoutingExampleTests/Routes Tests/ForgottenPasswordRouteTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// ForgottenPasswordRouteTests.swift
// RoutingExampleTests
//
// Created by Cassius Pacheco on 14/3/20.
// Copyright © 2020 Cassius Pacheco. All rights reserved.
//

import XCTest
@testable import RoutingExample

final class ForgottenPasswordRouteTests: XCTestCase {
func testOpenForgottenPassword() {
let router = RouterMock()
router.openForgottenPassword()

XCTAssertEqual(router.routeToCallsCount, 1)
XCTAssertTrue(router.routeToReceivedVC is ForgottenPasswordViewController, "The screen was presented as a ForgottenPasswordVC")
XCTAssertTrue(router.routeToReceivedTransition is PushTransition, "The screen was presented with a Push Transition")
}
}
21 changes: 21 additions & 0 deletions RoutingExampleTests/Routes Tests/LoginRouteTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// LoginRouteTests.swift
// RoutingExampleTests
//
// Created by Cassius Pacheco on 14/3/20.
// Copyright © 2020 Cassius Pacheco. All rights reserved.
//

import XCTest
@testable import RoutingExample

final class LoginRouteTests: XCTestCase {
func testOpenLogin() {
let router = RouterMock()
router.openLogin()

XCTAssertEqual(router.routeToCallsCount, 1)
XCTAssertTrue(router.routeToReceivedVC?.asNavigationsRootVC() is LoginViewController, "The screen was presented embedded in a Navigation Controller")
XCTAssertTrue(router.routeToReceivedTransition is ModalTransition, "The screen was presented with a Modal Transition")
}
}
Loading