Update integration test section
MODIFY
Samples/Tutorial/Tutorial5.md
Samples/Tutorial/Tutorial5.md
@@ -198,7 +198,7 @@ class TodoListWorkflowTests: XCTestCase {
}
.send(action: .new)
.verifyOutput { output in
- // THe `.new` action should emit a `.newTodo` output.
+ // The `.new` action should emit a `.newTodo` output.
switch output {
case .newTodo:
break // Expected
@@ -451,14 +451,13 @@ Add tests against the `render` methods of the `TodoEdit` and `TodoList` workflow
## Composition Testing
-We've demonstrated how to test leaf workflows for their actions and renderings. However, the power of workflow is the ability to compose a tree of workflows. The `RenderTester` provides the tools to test workflows with children.
+We've demonstrated how to test leaf workflows for their actions and renderings. However, the power of workflow is the ability to compose a tree of workflows. The `RenderTester` provides tools to test workflows with children.
+
+`ExpectedWorkflow` allows us to describe a child workflow that is expected to be rendered in the next render pass. It is given the type of child, an optional key, and the mock rendering to return. It can also provide an optional output:
-The `ExpectedWorkflow` allows a child workflow to be described that is expected for the next render. It is given the type of child, and optional key, and the mock rendering to return. It can also provide an optional output:
```swift
public struct ExpectedWorkflow {
-
public init<WorkflowType: Workflow>(type: WorkflowType.Type, key: String = "", rendering: WorkflowType.Rendering, output: WorkflowType.Output? = nil)
-
}
```
@@ -470,7 +469,6 @@ Start by adding `Equatable` conformance to the `State` to simplify the tests:
```swift
extension RootWorkflow {
-
// The state is an enum, and can either be on the welcome screen or the todo list.
// When on the todo list, it also includes the name provided on the welcome screen
enum State: Equatable {
@@ -493,16 +491,15 @@ import WorkflowTesting
@testable import WorkflowUI
class RootWorkflowTests: XCTestCase {
-
- func testWelcomeRendering() {
+ func testWelcomeRendering() throws {
RootWorkflow()
// Start in the `.welcome` state
- .renderTester(initialState: RootWorkflow.State.welcome)
+ .renderTester(initialState: .welcome)
// The `WelcomeWorkflow` is expected to be started in this render.
.expectWorkflow(
type: WelcomeWorkflow.self,
producingRendering: WelcomeScreen(
- name: "MyName",
+ name: "Ada",
onNameChanged: { _ in },
onLoginTapped: {}
)
@@ -514,12 +511,12 @@ class RootWorkflowTests: XCTestCase {
XCTFail("Expected first screen to be a `WelcomeScreen`")
return
}
- XCTAssertEqual("MyName", welcomeScreen.name)
+
+ XCTAssertEqual("Ada", welcomeScreen.name)
}
// Assert that no action was produced during this render, meaning our state remains unchanged
- .assertNoAction()
+ .assertNoOutput()
}
-
}
```
@@ -528,21 +525,20 @@ We needed to use a few `@testable` imports to inspect the underlying screen (sin
Now, we can also test the transition from the `.welcome` state to the `.todo` state:
```swift
- func testLogin() {
+ func testLogIn() throws {
RootWorkflow()
// Start in the `.welcome` state
- .renderTester(initialState: RootWorkflow.State.welcome)
+ .renderTester(initialState: .welcome)
// The `WelcomeWorkflow` is expected to be started in this render.
.expectWorkflow(
type: WelcomeWorkflow.self,
- // Simulate this as the `WelcomeScreen` returned by the `WelcomeWorkflow`. The callback can be stubbed out, as they won't be used.
producingRendering: WelcomeScreen(
- name: "MyName",
+ name: "Ada",
onNameChanged: { _ in },
onLoginTapped: {}
),
- // Simulate the `WelcomeWorkflow` sending an output of `.didLogin` as if the login button was tapped.
- producingOutput: .didLogin(name: "MyName")
+ // Simulate the `WelcomeWorkflow` sending an out0put of `.didLogIn` as if the "log in" button was tapped.
+ producingOutput: .didLogIn(name: "Ada")
)
// Now, validate that there is a single item in the BackStackScreen, which is our welcome screen (prior to the output).
.render { rendering in
@@ -551,10 +547,10 @@ Now, we can also test the transition from the `.welcome` state to the `.todo` st
XCTFail("Expected first screen to be a `WelcomeScreen`")
return
}
- XCTAssertEqual("MyName", welcomeScreen.name)
+ XCTAssertEqual("Ada", welcomeScreen.name)
}
// Assert that the state transitioned to `.todo`
- .assert(state: .todo(name: "MyName"))
+ .assert(state: .todo(name: "Ada"))
}
```
@@ -562,75 +558,69 @@ By simulating the output from the `WelcomeWorkflow`, we were able to drive the `
### TodoWorkflow Render Tests
-Now add tests for the `TodoWorkflow`, so that we have relatively full coverage. These are two examples, of selecting and saving a TODO to validate the transitions between screens, as well as updating the state in the parent (Add `Equatable` conformance to `TodoWorkflow.State` to simplify the tests):
+Now add tests for the `TodoWorkflow`, so that we have relatively full coverage. These are two examples, of selecting and saving a todo to validate the transitions between screens, as well as updating the state in the parent (Add `Equatable` conformance to `TodoWorkflow.State` to simplify the tests):
```swift
import XCTest
@testable import TutorialBase
import BackStackContainer
import WorkflowTesting
-
+import WorkflowUI
class TodoWorkflowTests: XCTestCase {
-
- func testSelectingTodo() {
+ func testSelectingTodo() throws {
let todos: [TodoModel] = [TodoModel(title: "Title", note: "Note")]
- TodoWorkflow(name: "MyName")
- // Start from the list step to validate selecting a todo:
+ TodoWorkflow(name: "Ada")
+ // Start from the list step to validate selecting a todo.
.renderTester(initialState: TodoWorkflow.State(
todos: todos,
step: .list
))
- // We only expect the TodoListWorkflow
+ // We only expect the TodoListWorkflow to be rendered.
.expectWorkflow(
type: TodoListWorkflow.self,
- producingRendering: BackStackScreen.Item(
- screen: TodoListScreen(
- todoTitles: ["Title"],
- onTodoSelected: { _ in }
- )
+ producingRendering: BackStackScreen<AnyScreen>.Item(
+ screen: TodoListScreen(todoTitles: ["Title"], onTodoSelected: { _ in }).asAnyScreen()
),
- // Simulate selecting the first todo:
- producingOutput: TodoListWorkflow.Output.selectTodo(index: 0)
+ // Simulate selecting the first todo.
+ producingOutput: .selectTodo(index: 0)
)
.render { items in
// Just validate that there is one item in the back stack.
- // Additional validation could be done on the screens returned if so desired.
+ // Additional validation could be done on the screens returned, if desired.
XCTAssertEqual(1, items.count)
}
- // Assert that the state was updated after the last render pass with the output from the TodoEditWorkflow.
- .assert(
- state: TodoWorkflow.State(
+ // Assert that the state was updated after the render pass with the output from the TodoListWorkflow.
+ .assert(state: TodoWorkflow.State(
todos: [TodoModel(title: "Title", note: "Note")],
step: .edit(index: 0)
- )
- )
+ ))
}
- func testSavingTodo() {
+ func testSavingTodo() throws {
let todos: [TodoModel] = [TodoModel(title: "Title", note: "Note")]
- TodoWorkflow(name: "MyName")
- // Start from the edit step so we can simulate saving:
+ TodoWorkflow(name: "Ada")
+ // Start from the edit step so we can simulate saving.
.renderTester(initialState: TodoWorkflow.State(
todos: todos,
step: .edit(index: 0)
))
- // We always expect the TodoListWorkflow
+ // We always expect the TodoListWorkflow to be rendered.
.expectWorkflow(
type: TodoListWorkflow.self,
- producingRendering: BackStackScreen.Item(
+ producingRendering: BackStackScreen<AnyScreen>.Item(
screen: TodoListScreen(
todoTitles: ["Title"],
onTodoSelected: { _ in }
).asAnyScreen()
)
)
- // Expect the TodoEditWorkflow. Additionally, simulate it emitting an output of ".save" to update the state.
+ // Expect the TodoEditWorkflow to be rendered as well (as we're on the edit step).
.expectWorkflow(
type: TodoEditWorkflow.self,
- producingRendering: BackStackScreen.Item(
+ producingRendering: BackStackScreen<AnyScreen>.Item(
screen: TodoEditScreen(
title: "Title",
note: "Note",
@@ -638,25 +628,23 @@ class TodoWorkflowTests: XCTestCase {
onNoteChanged: { _ in }
).asAnyScreen()
),
- producingOutput: TodoEditWorkflow.Output.save(TodoModel(
+ // Simulate it emitting an output of `.save` to update the state.
+ producingOutput: .save(TodoModel(
title: "Updated Title",
note: "Updated Note"
))
)
.render { items in
// Just validate that there are two items in the back stack.
- // Additional validation could be done on the screens returned if so desired.
+ // Additional validation could be done on the screens returned, if desired.
XCTAssertEqual(2, items.count)
}
- // Validate that the state was updated after the last render pass with the output from the TodoEditWorkflow.
- .assert(
- state: TodoWorkflow.State(
+ // Validate that the state was updated after the render pass with the output from the TodoEditWorkflow.
+ .assert(state: TodoWorkflow.State(
todos: [TodoModel(title: "Updated Title", note: "Updated Note")],
step: .list
- )
- )
+ ))
}
-
}
```
@@ -668,7 +656,8 @@ Add another test to `RootWorkflowTests`. We will run the tree of workflows in a
```swift
// RootWorkflowTests.swift
- func testAppFlow() {
+ func testAppFlow() throws {
+ // Note: You'll need to `import Workflow` in order to use `WorkflowHost`
let workflowHost = WorkflowHost(workflow: RootWorkflow())
// First rendering is just the welcome screen. Update the name.
@@ -681,10 +670,10 @@ Add another test to `RootWorkflowTests`. We will run the tree of workflows in a
return
}
- welcomeScreen.onNameChanged("MyName")
+ welcomeScreen.onNameChanged("Ada")
}
- // Log in and go to the welcome list
+ // Log in and go to the todo list.
do {
let backStack = workflowHost.rendering.value
XCTAssertEqual(1, backStack.items.count)
@@ -697,7 +686,7 @@ Add another test to `RootWorkflowTests`. We will run the tree of workflows in a
welcomeScreen.onLoginTapped()
}
- // Expect the todo list. Edit the first todo.
+ // Expect the todo list to be rendered. Edit the first todo.
do {
let backStack = workflowHost.rendering.value
XCTAssertEqual(2, backStack.items.count)
@@ -711,8 +700,10 @@ Add another test to `RootWorkflowTests`. We will run the tree of workflows in a
XCTFail("Expected second screen of `TodoListScreen`")
return
}
+
XCTAssertEqual(1, todoScreen.todoTitles.count)
- // Select the first todo:
+
+ // Select the first todo.
todoScreen.onTodoSelected(0)
}
@@ -732,11 +723,11 @@ Add another test to `RootWorkflowTests`. We will run the tree of workflows in a
}
guard let editScreen = backStack.items[2].screen.wrappedScreen as? TodoEditScreen else {
- XCTFail("Expected second screen of `TodoEditScreen`")
+ XCTFail("Expected third screen of `TodoEditScreen`")
return
}
- // Update the title:
+ // Update the title.
editScreen.onTitleChanged("New Title")
}
@@ -803,15 +794,14 @@ Add another test to `RootWorkflowTests`. We will run the tree of workflows in a
XCTFail("Expected second screen of `TodoListScreen`")
return
}
+
XCTAssertEqual(1, todoScreen.todoTitles.count)
XCTAssertEqual("New Title", todoScreen.todoTitles[0])
-
}
-
}
```
-This test was *very* verbose, and rather long. Generally, it's not recommended to do full integration tests like this (the action tests and render tests can give pretty solid coverage of a workflow's behavior). However, this is an example of how it might be done in the case that it's needed.
+This test was *very* verbose, and rather long. Generally, it's not recommended to do full integration tests like this (the action tests and render tests can give pretty solid coverage of a workflow's behavior). However, this is an example of how it might be done in case it's needed.
# Conclusion

Update integration test section