Repository: phoenixframework/phoenix
Pull request #3827 (2020-05-08 05:52:33)

add example for channel.push
it wasn't immediately obvious reading the documentation that I could receive replies from phoenix push events. I added an example

EDIT: wait, it's not working like that now for me.. I must have done something. I'll update when I figure out why.. :)

heavyk: that form of doing things worked for a bit, then it stopped working. I looked into it and it seems that in my code, I'm no longer getting the "ok" event right away, and instead I'm getting a "timeout" event, then the "ok" event. so, besides figuring out why I'm timing out so quickly (it's locahost), for whatever reason after timing out, it appears that no longer will I receive the ok because after timing out, it calls `reset` which calls `cancelRefEvent` which does `this.channel.off(this.refEvent)` which I think is what's preventing me from receiving the "ok" event after a timeout.

gonna take a walk. I'll see if my suspicion is right, and make another PR when I get back, if that is indeed the problem. cheers

Patch:
diff --git a/assets/js/phoenix.js b/assets/js/phoenix.js
index 0c2e579ad..a1297d590 100644
--- a/assets/js/phoenix.js
+++ b/assets/js/phoenix.js
@@ -503,6 +503,16 @@ export class Channel {
   canPush(){ return this.socket.isConnected() && this.isJoined() }
 
   /**
+   * Sends a message `event` to phoenix with the payload `payload`.
+   * Phoenix receives this in the `handle_in(event, payload, socket)`
+   * function. if phoenix replies or it times out (default 10000ms),
+   * then optionally the reply can be received.
+   *
+   * @example
+   * channel.push("event")
+   *   .receive("ok", payload => console.log("phoenix replied:", payload))
+   *   .receive("error", err => console.log("phoenix errored", err))
+   *   .receive("timeout", () => console.log("timed out pushing"))
    * @param {string} event
    * @param {Object} payload
    * @param {number} [timeout]
diff --git a/guides/deployment/deployment.md b/guides/deployment/deployment.md
index 3834acb1c..cb287a3dd 100644
--- a/guides/deployment/deployment.md
+++ b/guides/deployment/deployment.md
@@ -52,6 +52,8 @@ $ mix phx.digest
 Check your digested files at "priv/static".
 ```
 
+*Note:* the `--prefix` flag on `npm` may not work on Windows. If so, replace the first command by `cd assets && npm run deploy && cd ..`.
+
 And that is it! The first command builds the assets and the second generates digests as well as a cache manifest file so Phoenix can quickly serve assets in production.
 
 Keep in mind that, if you by any chance forget to run the steps above, Phoenix will show an error message:
diff --git a/guides/deployment/heroku.md b/guides/deployment/heroku.md
index 9eeba9634..307782649 100644
--- a/guides/deployment/heroku.md
+++ b/guides/deployment/heroku.md
@@ -35,7 +35,7 @@ Let's separate this process into a few steps so we can keep track of where we ar
 - Initialize Git repository
 - Sign up for Heroku
 - Install the Heroku Toolbelt
-- Create and setup Heroku application
+- Create and set up Heroku application
 - Make our project ready for Heroku
 - Deploy time!
 - Useful Heroku commands
@@ -68,9 +68,9 @@ Once we have signed up, we can download the correct version of the Heroku Toolbe
 
 The Heroku CLI, part of the Toolbelt, is useful to create Heroku applications, list currently running dynos for an existing application, tail logs or run one-off commands (mix tasks for instance).
 
-## Create and Setup Heroku Application
+## Create and Set Up Heroku Application
 
-There are two different ways to deploy a Phoenix app on Heroku. We could use Heroku buildpacks or their container stack. The difference between these two approaches is in how we tell Heroku to treat our build. In buildpack case, we need to update our apps configuration on Heroku to use Phoenix/Elixir specific buildpacks. On container approach, we have more control on how we want to setup our app and we can define our container image using `Dockerfile` and `heroku.yml`. This section will explore the buildpack approach. In order to use Dockerfile, it is often recommended to convert our app to use releases, which we will describe later on.
+There are two different ways to deploy a Phoenix app on Heroku. We could use Heroku buildpacks or their container stack. The difference between these two approaches is in how we tell Heroku to treat our build. In buildpack case, we need to update our apps configuration on Heroku to use Phoenix/Elixir specific buildpacks. On container approach, we have more control on how we want to set up our app and we can define our container image using `Dockerfile` and `heroku.yml`. This section will explore the buildpack approach. In order to use Dockerfile, it is often recommended to convert our app to use releases, which we will describe later on.
 
 ### Create Application
 
@@ -350,11 +350,11 @@ build:
     DATABASE_URL: $DATABASE_URL
 ```
 
-### Setup releases and Dockerfile
+### Set up releases and Dockerfile
 
 Now we need to define a `Dockerfile` at the root folder of your project that contains your application. We recommend to use releases when doing so, as the release will allow us to build a container with only the parts of Erlang and Elixir we actually use. Follow [the releases docs](releases.html). At the end of the guide, there is a sample Dockerfile file you can use.
 
-Once you have the image definition setup, you can push your app to heroku and you can see it starts building the image and deploy it.
+Once you have the image definition set up, you can push your app to heroku and you can see it starts building the image and deploy it.
 
 ## Useful Heroku Commands
 
diff --git a/guides/deployment/releases.md b/guides/deployment/releases.md
index dfaef3bf8..0bd3a5ccb 100644
--- a/guides/deployment/releases.md
+++ b/guides/deployment/releases.md
@@ -45,6 +45,8 @@ $ npm run deploy --prefix ./assets
 $ mix phx.digest
 ```
 
+*Note:* the `--prefix` flag on `npm` may not work on Windows. If so, replace the first command by `cd assets && npm run deploy && cd ..`.
+
 And now run `mix release`:
 
 ```console
diff --git a/guides/ecto.md b/guides/ecto.md
index ff4dd01f8..c2dd6dfae 100644
--- a/guides/ecto.md
+++ b/guides/ecto.md
@@ -470,7 +470,7 @@ defmodule HelloPhoenix.MixProject do
     [
       {:phoenix, "~> 1.4.0"},
       {:phoenix_ecto, "~> 4.0"},
-      {:ecto_sql, "~> 3.4"},
+      {:ecto_sql, "~> 3.4.4"},
       {:myxql, ">= 0.0.0"},
       {:phoenix_html, "~> 2.11"},
       {:phoenix_live_reload, "~> 1.2", only: :dev},
diff --git a/guides/howto/custom_error_pages.md b/guides/howto/custom_error_pages.md
index 4bb24de34..09872b5d2 100644
--- a/guides/howto/custom_error_pages.md
+++ b/guides/howto/custom_error_pages.md
@@ -49,7 +49,7 @@ def template_not_found(template, _assigns) do
 end
 ```
 
-Great, so we have this `template_not_found/2` function that takes a template and an `assigns` map, which we ignore. The `template_not_found/2` is invoked whenever a Phoenix.View attempts to render a template bu tno template is found.
+Great, so we have this `template_not_found/2` function that takes a template and an `assigns` map, which we ignore. The `template_not_found/2` is invoked whenever a Phoenix.View attempts to render a template but no template is found.
 
 In order words, to provide custom error pages, we could simply define a the proper `render/2` function clause in `HelloWeb.ErrorView`.
 
diff --git a/guides/request_lifecycle.md b/guides/request_lifecycle.md
index efd419343..1f5241f52 100644
--- a/guides/request_lifecycle.md
+++ b/guides/request_lifecycle.md
@@ -8,7 +8,7 @@ Let's get on with our first new Phoenix page!
 
 ## Adding a new page
 
-When your browser accesses [http://localhost:4000/](http://localhost:4000/), it sends a HTTP request to whatever service is running on that address, in this our Phoenix application. The HTTP request is made of a verb and a path. For example, the following browser requests translate into:
+When your browser accesses [http://localhost:4000/](http://localhost:4000/), it sends a HTTP request to whatever service is running on that address, in this case our Phoenix application. The HTTP request is made of a verb and a path. For example, the following browser requests translate into:
 
 | Browser address bar                | Verb | Path          |
 |:-----------------------------------|:-----|:--------------|
@@ -147,7 +147,7 @@ which injects our template into the layout before the HTML is sent off to the br
 
 As we built our first page, we could start to understand how the request life-cycle is put together. Now let's take a more holistic look at it.
 
-All HTTP requests start in our application endpoint. You can find it as a module named `HelloWeb.Endpoint` in `lib/hello_web/endpoint.ex`. Once you open up the endpoint file, you will see that, similar to the router, the endpoint has many calls to `plug`. `Plug` is a library and specification for stiching web applications together. It is an essential part of how Phoenix handle requests and we will discuss it in detail [in the Plug guide](plug.html) coming next.
+All HTTP requests start in our application endpoint. You can find it as a module named `HelloWeb.Endpoint` in `lib/hello_web/endpoint.ex`. Once you open up the endpoint file, you will see that, similar to the router, the endpoint has many calls to `plug`. `Plug` is a library and specification for stitching web applications together. It is an essential part of how Phoenix handle requests and we will discuss it in detail [in the Plug guide](plug.html) coming next.
 
 For now, it suffices to say that each Plug defines a slice of request processing. In the endpoint you will find a skeleton roughly like this:
 
diff --git a/guides/telemetry.md b/guides/telemetry.md
index 4386868d1..1c11b9236 100644
--- a/guides/telemetry.md
+++ b/guides/telemetry.md
@@ -13,6 +13,14 @@ and we will guide you through the steps to properly
 instrument your own functions using `:telemetry`. Let's take
 a closer look at how Telemetry works in your application.
 
+## Overview
+
+The `[:telemetry]` library allows you to emit events at various stages of an application's lifecycle. You can then respond to these events by, among other things, aggregating them as metrics and sending the metrics data to a reporting destination.
+
+Telemetry stores events by their name in an ETS table, along with the handler for each event. Then, when a given event is executed, Telemetry looks up its handler and invokes it.
+
+Phoenix's Telemetry tooling provides you with a supervisor that uses `Telemetry.Metrics` to define the list of Telemetry events to handle and how to handle those events, i.e. how to structure them as a certain type of metric. This supervisor works together with Telemetry reporters to respond to the specified Telemetry events by aggregating them as the appropriate metric and sending them to the correct reporting destination.
+
 ## The Telemetry supervisor
 
 Since v1.5, new Phoenix applications are generated with a
@@ -103,7 +111,7 @@ children = [
 ## Telemetry Events
 
 Many Elixir libraries (including Phoenix) are already using
-the `:telemetry` package (http://hexdocs.pm/telemetry) as a
+the [`:telemetry`](http://hexdocs.pm/telemetry) package as a
 way to give users more insight into the behavior of their
 applications, by emitting events at key moments in the
 application lifecycle.
@@ -124,7 +132,7 @@ A Telemetry event is made up of the following:
 Here is an example of an event from your endpoint:
 
 * `[:phoenix, :endpoint, :stop]` - dispatched by
-  `Plug.Telemetry` in your endpoint whenever the response is
+  `Plug.Telemetry`, one of the default plugs in your endpoint, whenever the response is
   sent
 
   * Measurement: `%{duration: native_time}`
@@ -148,11 +156,15 @@ took to get the response:
 > &#x2015; `Telemetry.Metrics`
 
 The Telemetry.Metrics package provides a common interface
-for defining metrics. What this means is that the package
-does not perform any aggregation of the measurements itself,
-but instead relies on reporters to do so. We will discuss
+for defining metrics. It exposes a set of [five metric type functions](https://hexdocs.pm/telemetry_metrics/Telemetry.Metrics.html#module-metrics) that are responsible for structuring a given Telemetry event as a particular measurement.
+
+The package does not perform any aggregation of the measurements itself. Instead, it provides a reporter with the Telemetry event-as-measurement definition and the reporter uses that definition to perform aggregations and report them.
+
+We will discuss
 reporters in the next section.
 
+Let's take a look at some examples.
+
 Using `Telemetry.Metrics`, you can define a counter metric,
 which counts how many HTTP requests were completed:
 
@@ -182,11 +194,30 @@ documentation.
 
 ### An Ecto Example
 
-Like Phoenix, Ecto ships with built-in telemetry events.
+Like Phoenix, Ecto ships with built-in Telemetry events.
 This means that you can gain introspection into your web
 and database layers using the same tools.
 
-For instance, you might want to graph query execution time:
+Here is an example of a Telemetry event executed by Ecto when an Ecto repository starts:
+
+* `[:ecto, :repo, :init]` - dispatched by
+  `Ecto.Repo.Supervisor`
+
+  * Measurement: `%{system_time: native_time}`
+
+  * Metadata: `%{repo: Ecto.Repo, opts: Keyword.t()}`
+
+This means that whenever the `Ecto.Repo.Supervisor` starts, it will emit an event, via `:telemetry`,
+with a measurement of the time at start-up.
+
+```elixir
+:telemetry.execute([:ecto, :repo, :init], %{system_time: System.system_time()}, %{repo: repo, opts: opts})
+```
+
+Additional Telemetry events are executed by Ecto adapters.
+
+One such adapter-specific event is the `[:my_app, :repo, :query]` event.
+For instance, if you want to graph query execution time, you can use the `Telemetry.Metrics.summary/2` function to instruct your reporter to calculate statistics of the `[:my_app, :repo, :query]` event, like maximum, mean, percentiles etc.:
 
 ```elixir
 Telemetry.Metrics.summary("my_app.repo.query.query_time",
@@ -194,7 +225,7 @@ Telemetry.Metrics.summary("my_app.repo.query.query_time",
 )
 ```
 
-or view how long queries spend queued:
+Or you could use the `Telemetry.Metrics.distribution/2` function to define a histogram for another adapter-specific event: `[:my_app, :repo, :query, :queue_time]`, thus visualizing how long queries spend queued:
 
 ```elixir
 Telemetry.Metrics.distribution("my_app.repo.query.queue_time",
@@ -220,6 +251,16 @@ interface provided by `Telemetry.Metrics`. They then
 aggregate the measurements (data) into metrics to provide
 meaningful information about your application.
 
+For example, if the following `Telemetry.Metrics.summary/2` call is added to the `metrics/0` function of your Telemetry supervisor:
+
+```elixir
+summary("phoenix.endpoint.stop.duration",
+  unit: {:native, :millisecond}
+)
+```
+
+Then the reporter will attach a listener for the `"phoenix.endpoint.stop.duration"` event and will respond to this event by calculating a summary metric with the given event metadata and reporting on that metric to the appropriate source.
+
 ### Phoenix.LiveDashboard
 
 For developers interested in real-time visualizations for
diff --git a/installer/lib/phx_new/generator.ex b/installer/lib/phx_new/generator.ex
index de1fd517a..6d106a372 100644
--- a/installer/lib/phx_new/generator.ex
+++ b/installer/lib/phx_new/generator.ex
@@ -233,15 +233,29 @@ defmodule Phx.New.Generator do
     Mix.raise "Unknown database #{inspect db}"
   end
 
-  defp db_config(app, module, user, pass) do
-    [dev:  [username: user, password: pass, database: "#{app}_dev", hostname: "localhost",
-            show_sensitive_data_on_connection_error: true],
-     test: [username: user, password: pass, database: {:literal, ~s|"#{app}_test\#{System.get_env("MIX_TEST_PARTITION")}"|}, hostname: "localhost",
-            pool: Ecto.Adapters.SQL.Sandbox],
-     test_setup_all: "Ecto.Adapters.SQL.Sandbox.mode(#{inspect module}.Repo, :manual)",
-     test_setup: ":ok = Ecto.Adapters.SQL.Sandbox.checkout(#{inspect module}.Repo)",
-     test_async: "Ecto.Adapters.SQL.Sandbox.mode(#{inspect module}.Repo, {:shared, self()})"]
-  end
+defp db_config(app, module, user, pass) do
+  [
+    dev: [
+      username: user,
+      password: pass,
+      database: "#{app}_dev",
+      hostname: "localhost",
+      show_sensitive_data_on_connection_error: true
+    ],
+    test: [
+      username: user,
+      password: pass,
+      database: {:literal, ~s|"#{app}_test\#{System.get_env("MIX_TEST_PARTITION")}"|},
+      hostname: "localhost",
+      pool: Ecto.Adapters.SQL.Sandbox
+    ],
+    test_setup_all: "Ecto.Adapters.SQL.Sandbox.mode(#{inspect(module)}.Repo, :manual)",
+    test_setup: """
+        pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{inspect(module)}.Repo, shared: not tags[:async])
+        on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)\
+    """
+  ]
+end
 
   defp kw_to_config(kw) do
     Enum.map(kw, fn
diff --git a/installer/templates/phx_ecto/data_case.ex b/installer/templates/phx_ecto/data_case.ex
index 9860f88c7..7c620655f 100644
--- a/installer/templates/phx_ecto/data_case.ex
+++ b/installer/templates/phx_ecto/data_case.ex
@@ -28,12 +28,7 @@ defmodule <%= app_module %>.DataCase do
   end
 
   setup tags do
-    <%= adapter_config[:test_setup] %>
-
-    unless tags[:async] do
-      <%= adapter_config[:test_async] %>
-    end
-
+<%= adapter_config[:test_setup] %>
     :ok
   end
 
diff --git a/installer/templates/phx_single/README.md b/installer/templates/phx_single/README.md
index f3b0f8283..dc79271bd 100644
--- a/installer/templates/phx_single/README.md
+++ b/installer/templates/phx_single/README.md
@@ -2,7 +2,9 @@
 
 To start your Phoenix server:
 
-  * Setup the project with `mix setup`
+  * Install dependencies with `mix deps.get`<%= if ecto do %>
+  * Create and migrate your database with `mix ecto.setup`<% end %><%= if webpack do %>
+  * Install Node.js dependencies with `npm install` inside the `assets` directory<% end %>
   * Start Phoenix endpoint with `mix phx.server`
 
 Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
diff --git a/installer/templates/phx_single/mix.exs b/installer/templates/phx_single/mix.exs
index e0d59020a..5df7bc9b0 100644
--- a/installer/templates/phx_single/mix.exs
+++ b/installer/templates/phx_single/mix.exs
@@ -39,13 +39,13 @@ defmodule <%= app_module %>.MixProject do
     [
       <%= phoenix_dep %>,<%= if ecto do %>
       {:phoenix_ecto, "~> 4.1"},
-      {:ecto_sql, "~> 3.4"},
+      {:ecto_sql, "~> 3.4.4"},
       {<%= inspect adapter_app %>, ">= 0.0.0"},<% end %><%= if html do %><%= if live do %>
       {:phoenix_live_view, "~> 0.12.0"},
       {:floki, ">= 0.0.0", only: :test},<% end %>
       {:phoenix_html, "~> 2.11"},
       {:phoenix_live_reload, "~> 1.2", only: :dev},<% end %><%= if dashboard do %>
-      {:phoenix_live_dashboard, "~> 0.2.0"},<% end %>
+      {:phoenix_live_dashboard, "~> 0.2"},<% end %>
       {:telemetry_metrics, "~> 0.4"},
       {:telemetry_poller, "~> 0.4"},<%= if gettext do %>
       {:gettext, "~> 0.11"},<% end %>
diff --git a/installer/templates/phx_umbrella/apps/app_name/mix.exs b/installer/templates/phx_umbrella/apps/app_name/mix.exs
index dcdf89b83..3ccc163f0 100644
--- a/installer/templates/phx_umbrella/apps/app_name/mix.exs
+++ b/installer/templates/phx_umbrella/apps/app_name/mix.exs
@@ -37,7 +37,7 @@ defmodule <%= app_module %>.MixProject do
   defp deps do
     [
       {:phoenix_pubsub, "~> 2.0"}<%= if ecto do %>,
-      {:ecto_sql, "~> 3.4"},
+      {:ecto_sql, "~> 3.4.4"},
       {:<%= adapter_app %>, ">= 0.0.0"},
       {:jason, "~> 1.0"}<% end %>
     ]
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/README.md b/installer/templates/phx_umbrella/apps/app_name_web/README.md
index d7a097f84..8a7038673 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/README.md
+++ b/installer/templates/phx_umbrella/apps/app_name_web/README.md
@@ -2,7 +2,9 @@
 
 To start your Phoenix server:
 
-  * Setup the project with `mix setup`
+  * Install dependencies with `mix deps.get`<%= if ecto do %>
+  * Create and migrate your database with `mix ecto.setup`<% end %><%= if webpack do %>
+  * Install Node.js dependencies with `npm install` inside the `assets` directory<% end %>
   * Start Phoenix endpoint with `mix phx.server`
 
 Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
index 09666cdd0..1384f5d56 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
@@ -43,7 +43,7 @@ defmodule <%= web_namespace %>.MixProject do
       {:floki, ">= 0.0.0", only: :test},<% end %>
       {:phoenix_html, "~> 2.11"},
       {:phoenix_live_reload, "~> 1.2", only: :dev},<% end %><%= if dashboard do %>
-      {:phoenix_live_dashboard, "~> 0.2.0"},<% end %>
+      {:phoenix_live_dashboard, "~> 0.2"},<% end %>
       {:telemetry_metrics, "~> 0.4"},
       {:telemetry_poller, "~> 0.4"},<%= if gettext do %>
       {:gettext, "~> 0.11"},<% end %><%= if app_name != web_app_name do %>
diff --git a/lib/mix/phoenix/context.ex b/lib/mix/phoenix/context.ex
index ae9849fde..97a5dbfe4 100644
--- a/lib/mix/phoenix/context.ex
+++ b/lib/mix/phoenix/context.ex
@@ -12,6 +12,7 @@ defmodule Mix.Phoenix.Context do
             basename: nil,
             file: nil,
             test_file: nil,
+            test_fixtures_file: nil,
             dir: nil,
             generate?: true,
             context_app: nil,
@@ -32,6 +33,8 @@ defmodule Mix.Phoenix.Context do
     file      = dir <> ".ex"
     test_dir  = Mix.Phoenix.context_test_path(ctx_app, basedir)
     test_file = test_dir <> "_test.exs"
+    test_fixtures_dir = Mix.Phoenix.context_app_path(ctx_app, "test/support/fixtures")
+    test_fixtures_file = Path.join([test_fixtures_dir, basedir <> "_fixtures.ex"])
     generate? = Keyword.get(opts, :context, true)
 
     %Context{
@@ -44,6 +47,7 @@ defmodule Mix.Phoenix.Context do
       basename: basename,
       file: file,
       test_file: test_file,
+      test_fixtures_file: test_fixtures_file,
       dir: dir,
       generate?: generate?,
       context_app: ctx_app,
@@ -54,6 +58,8 @@ defmodule Mix.Phoenix.Context do
 
   def pre_existing_tests?(%Context{test_file: file}), do: File.exists?(file)
 
+  def pre_existing_test_fixtures?(%Context{test_fixtures_file: file}), do: File.exists?(file)
+
   def function_count(%Context{file: file}) do
     {_ast, count} =
       file
diff --git a/lib/mix/tasks/phx.gen.context.ex b/lib/mix/tasks/phx.gen.context.ex
index 9f57e2cd7..e04736ef0 100644
--- a/lib/mix/tasks/phx.gen.context.ex
+++ b/lib/mix/tasks/phx.gen.context.ex
@@ -137,6 +137,7 @@ defmodule Mix.Tasks.Phx.Gen.Context do
     if schema.generate?, do: Gen.Schema.copy_new_files(schema, paths, binding)
     inject_schema_access(context, paths, binding)
     inject_tests(context, paths, binding)
+    inject_test_fixture(context, paths, binding)
 
     context
   end
@@ -165,6 +166,16 @@ defmodule Mix.Tasks.Phx.Gen.Context do
     |> inject_eex_before_final_end(test_file, binding)
   end
 
+  defp inject_test_fixture(%Context{test_fixtures_file: test_fixtures_file} = context, paths, binding) do
+    unless Context.pre_existing_test_fixtures?(context) do
+      Mix.Generator.create_file(test_fixtures_file, Mix.Phoenix.eval_from(paths, "priv/templates/phx.gen.context/fixtures_module.ex", binding))
+    end
+
+    paths
+    |> Mix.Phoenix.eval_from("priv/templates/phx.gen.context/fixtures.ex", binding)
+    |> inject_eex_before_final_end(test_fixtures_file, binding)
+  end
+
   defp inject_eex_before_final_end(content_to_inject, file_path, binding) do
     file = File.read!(file_path)
 
diff --git a/lib/phoenix/channel.ex b/lib/phoenix/channel.ex
index f99ca0498..a927eaf4e 100644
--- a/lib/phoenix/channel.ex
+++ b/lib/phoenix/channel.ex
@@ -167,12 +167,16 @@ defmodule Phoenix.Channel do
 
   `terminate/2`, however, won't be invoked in case of errors nor in
   case of exits. This is the same behaviour as you find in Elixir
-  abstractions like `GenServer` and others. Typically speaking, if you
-  want to clean something up, it is better to monitor your channel
-  process and do the clean up from another process.  Similar to GenServer,
+  abstractions like `GenServer` and others. Similar to `GenServer`,
   it would also be possible `:trap_exit` to guarantee that `terminate/2`
   is invoked. This practice is not encouraged though.
 
+  Typically speaking, if you want to clean something up, it is better to
+  monitor your channel process and do the clean up from another process.
+  All channel callbacks including `join/3` are called from within the
+  channel process. Therefore, `self()` in any of them returns the PID to
+  be monitored.
+
   ## Exit reasons when stopping a channel
 
   When the channel callbacks return a `:stop` tuple, such as:
diff --git a/lib/phoenix/channel/server.ex b/lib/phoenix/channel/server.ex
index cd402e234..3c3c5a906 100644
--- a/lib/phoenix/channel/server.ex
+++ b/lib/phoenix/channel/server.ex
@@ -22,10 +22,11 @@ defmodule Phoenix.Channel.Server do
 
     ref = make_ref()
     from = {self(), ref}
-    child_spec = channel.child_spec({payload, from, socket})
+    child_spec = channel.child_spec({socket.endpoint, from})
 
     case PoolSupervisor.start_child(socket.endpoint, socket.handler, from, child_spec) do
       {:ok, pid} ->
+        send(pid, {Phoenix.Channel, payload, from, socket})
         mon_ref = Process.monitor(pid)
 
         receive do
@@ -254,10 +255,8 @@ defmodule Phoenix.Channel.Server do
   ## Callbacks
 
   @doc false
-  def init({auth_payload, from, socket}) do
-    # TODO: Use handle_continue when we support Erlang/OTP 21+.
-    send(self(), {:join, __MODULE__})
-    {:ok, {auth_payload, from, socket}}
+  def init({_endpoint, {pid, _}}) do
+    {:ok, Process.monitor(pid)}
   end
 
   @doc false
@@ -285,7 +284,8 @@ defmodule Phoenix.Channel.Server do
   end
 
   @doc false
-  def handle_info({:join, __MODULE__}, {auth_payload, {pid, _} = from, socket}) do
+  def handle_info({Phoenix.Channel, auth_payload, {pid, _} = from, socket}, ref) do
+    Process.demonitor(ref)
     %{channel: channel, topic: topic, private: private} = socket
     Process.put(:"$callers", [pid])
 
@@ -329,6 +329,10 @@ defmodule Phoenix.Channel.Server do
     |> handle_result(:handle_out)
   end
 
+  def handle_info({:DOWN, ref, _, _, reason}, ref) do
+    {:stop, reason, ref}
+  end
+
   def handle_info({:DOWN, _, _, transport_pid, reason}, %{transport_pid: transport_pid} = socket) do
     reason = if reason == :normal, do: {:shutdown, :closed}, else: reason
     {:stop, reason, socket}
@@ -395,8 +399,23 @@ defmodule Phoenix.Channel.Server do
 
   defp init_join(socket, channel, topic) do
     %{transport_pid: transport_pid, serializer: serializer, pubsub_server: pubsub_server} = socket
-    Process.monitor(transport_pid)
 
+    unless pubsub_server do
+      raise """
+      The :pubsub_server was not configured for endpoint #{inspect(socket.endpoint)}.
+      Make sure to start a PubSub process in your application supervision tree:
+
+          {Phoenix.PubSub, [name: YOURAPP.PubSub, adapter: Phoenix.PubSub.PG2]}
+
+      And then add it to your endpoint config:
+
+          config :YOURAPP, YOURAPPWeb.Endpoint,
+            # ...
+            pubsub_server: YOURAPP.PubSub
+      """
+    end
+
+    Process.monitor(transport_pid)
     fastlane = {:fastlane, transport_pid, serializer, channel.__intercepts__()}
     PubSub.subscribe(pubsub_server, topic, metadata: fastlane)
 
diff --git a/lib/phoenix/digester.ex b/lib/phoenix/digester.ex
index 2c90453aa..26c898e7d 100644
--- a/lib/phoenix/digester.ex
+++ b/lib/phoenix/digester.ex
@@ -119,7 +119,7 @@ defmodule Phoenix.Digester do
 
   defp compiled_file?(file_path) do
     Regex.match?(@digested_file_regex, Path.basename(file_path)) ||
-      Path.extname(file_path) == ".gz" ||
+      Path.extname(file_path) in ~w(.gz .br) ||
       Path.basename(file_path) == "cache_manifest.json"
   end
 
diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex
index b1a79bae5..7b14c4dc7 100644
--- a/lib/phoenix/endpoint.ex
+++ b/lib/phoenix/endpoint.ex
@@ -688,12 +688,12 @@ defmodule Phoenix.Endpoint do
 
     * `:websocket` - controls the websocket configuration.
       Defaults to `true`. May be false or a keyword list
-      of options. See "Shared configuration" and
+      of options. See "Common configuration" and
       "WebSocket configuration" for the whole list
 
     * `:longpoll` - controls the longpoll configuration.
       Defaults to `false`. May be true or a keyword list
-      of options. See "Shared configuration" and
+      of options. See "Common configuration" and
       "Longpoll configuration" for the whole list
 
   If your socket is implemented using `Phoenix.Socket`,
diff --git a/lib/phoenix/endpoint/cowboy2_adapter.ex b/lib/phoenix/endpoint/cowboy2_adapter.ex
index 7047dcc0e..3ad326dfa 100644
--- a/lib/phoenix/endpoint/cowboy2_adapter.ex
+++ b/lib/phoenix/endpoint/cowboy2_adapter.ex
@@ -16,7 +16,7 @@ defmodule Phoenix.Endpoint.Cowboy2Adapter do
 
     * `:drainer` - a drainer process that triggers when your application is
       shutting to wait for any on-going request to finish. It accepts all
-      options as defined by [`Plug.Cowboy`](https://hexdocs.pm/plug_cowboy/Plug.Cowboy.Drainer.httml).
+      options as defined by [`Plug.Cowboy`](https://hexdocs.pm/plug_cowboy/Plug.Cowboy.Drainer.html).
       Defaults to `[]` and can be disabled by setting it to false.
 
   ## Custom dispatch options
diff --git a/lib/phoenix/endpoint/cowboy2_handler.ex b/lib/phoenix/endpoint/cowboy2_handler.ex
index 51b448537..97fd2aba6 100644
--- a/lib/phoenix/endpoint/cowboy2_handler.ex
+++ b/lib/phoenix/endpoint/cowboy2_handler.ex
@@ -60,9 +60,10 @@ defmodule Phoenix.Endpoint.Cowboy2Handler do
 
         {:plug, conn, handler, opts} ->
           %{adapter: {@connection, req}} =
-            conn
-            |> handler.call(opts)
-            |> maybe_send(handler)
+            conn =
+              conn
+              |> handler.call(opts)
+              |> maybe_send(handler)
 
           :telemetry.execute(
             [:plug_adapter, :call, :stop],
diff --git a/lib/phoenix/endpoint/supervisor.ex b/lib/phoenix/endpoint/supervisor.ex
index f3a255c2f..b9f150c21 100644
--- a/lib/phoenix/endpoint/supervisor.ex
+++ b/lib/phoenix/endpoint/supervisor.ex
@@ -285,10 +285,9 @@ defmodule Phoenix.Endpoint.Supervisor do
   end
 
   defp build_url(endpoint, url) do
-    build_url(endpoint.config(:https), endpoint.config(:http), url)
-  end
+    https = endpoint.config(:https)
+    http  = endpoint.config(:http)
 
-  defp build_url(https, http, url) do
     {scheme, port} =
       cond do
         https ->
@@ -303,6 +302,10 @@ defmodule Phoenix.Endpoint.Supervisor do
     host   = host_to_binary(url[:host] || "localhost")
     port   = port_to_integer(url[:port] || port)
 
+    if host =~ ":" do
+      Logger.warn("url: [host: ...] configuration value #{inspect(host)} for #{inspect(endpoint)} is invalid")
+    end
+
     %URI{scheme: scheme, port: port, host: host}
   end
 
diff --git a/lib/phoenix/endpoint/watcher.ex b/lib/phoenix/endpoint/watcher.ex
index 928f07ea2..e6c2de7e1 100644
--- a/lib/phoenix/endpoint/watcher.ex
+++ b/lib/phoenix/endpoint/watcher.ex
@@ -47,13 +47,13 @@ defmodule Phoenix.Endpoint.Watcher do
       !System.find_executable("node") ->
         Logger.error "Could not start watcher because \"node\" is not available. Your Phoenix " <>
                      "application is still running, however assets won't be compiled. " <>
-                     "You may fix this by installing \"node\" and then running \"npm install --prefix assets\"."
+                     "You may fix this by installing \"node\" and then running \"npm install\" inside the \"assets\" directory."
         exit(:shutdown)
 
       not File.exists?(script_path) ->
         Logger.error "Could not start node watcher because script #{inspect script_path} does not " <>
                      "exist. Your Phoenix application is still running, however assets " <>
-                     "won't be compiled. You may fix this by running \"npm install --prefix assets\"."
+                     "won't be compiled. You may fix this by running \"npm install\" inside the \"assets\" directory."
         exit(:shutdown)
 
       true -> :ok
diff --git a/lib/phoenix/logger.ex b/lib/phoenix/logger.ex
index 2d859d805..3460a55b1 100644
--- a/lib/phoenix/logger.ex
+++ b/lib/phoenix/logger.ex
@@ -69,7 +69,7 @@ defmodule Phoenix.Logger do
 
     * `[:phoenix, :channel_joined]` - dispatched at the end of a channel join
       * Measurement: `%{duration: native_time}`
-      * Metadata: `%{params: term, socket: Phoenix.Socket.t}`
+      * Metadata: `%{result: :ok | :error, params: term, socket: Phoenix.Socket.t}`
       * Disable logging: This event cannot be disabled
 
     * `[:phoenix, :channel_handled_in]` - dispatched at the end of a channel handle in
diff --git a/lib/phoenix/presence.ex b/lib/phoenix/presence.ex
index a5cf27dab..63596a8a2 100644
--- a/lib/phoenix/presence.ex
+++ b/lib/phoenix/presence.ex
@@ -73,8 +73,8 @@ defmodule Phoenix.Presence do
   The diff structure will be a map of `:joins` and `:leaves` of the form:
 
       %{
-        joins: %{"123" => %{metas: [%{status: "away", phx_ref: ...}]},
-        leaves: %{"456" => %{metas: [%{status: "online", phx_ref: ...}]
+        joins: %{"123" => %{metas: [%{status: "away", phx_ref: ...}]}},
+        leaves: %{"456" => %{metas: [%{status: "online", phx_ref: ...}]}}
       },
 
   See `c:list/2` for more information on the presence data structure.
@@ -97,7 +97,7 @@ defmodule Phoenix.Presence do
   information to include any additional information. For example:
 
       def fetch(_topic, presences) do
-        users = entries |> Map.keys() |> Accounts.get_users_map()
+        users = presences |> Map.keys() |> Accounts.get_users_map()
 
         for {key, %{metas: metas}} <- presences, into: %{} do
           {key, %{metas: metas, user: users[key]}}
diff --git a/lib/phoenix/router.ex b/lib/phoenix/router.ex
index ddd72b00b..a4662e6c1 100644
--- a/lib/phoenix/router.ex
+++ b/lib/phoenix/router.ex
@@ -531,7 +531,7 @@ defmodule Phoenix.Router do
 
   defp build_pipes(name, pipe_through) do
     plugs = pipe_through |> Enum.reverse |> Enum.map(&{&1, [], true})
-    {conn, body} = Plug.Builder.compile(__ENV__, plugs, init_mode: Phoenix.plug_init_mode())
+    {conn, body} = Plug.Builder.compile(__ENV__, plugs, init_mode: Phoenix.plug_init_mode(), log_on_halt: :debug)
 
     quote do
       defp unquote(name)(unquote(conn)), do: unquote(body)
diff --git a/lib/phoenix/socket.ex b/lib/phoenix/socket.ex
index a5fff3d6a..8c84808e0 100644
--- a/lib/phoenix/socket.ex
+++ b/lib/phoenix/socket.ex
@@ -129,9 +129,14 @@ defmodule Phoenix.Socket do
   ## Custom channels
 
   You can list any module as a channel as long as it implements
-  a `start_link/1` function that receives a tuple with three elements:
+  a `child_spec/1` function. The `child_spec/1` function receives
+  the caller as argument and it must return a child spec that
+  initializes a process.
 
-      {auth_payload, from, socket}
+  Once the process is initialized, it will receive the following
+  message:
+
+      {Phoenix.Channel, auth_payload, from, socket}
 
   A custom channel implementation MUST invoke
   `GenServer.reply(from, {:ok | :error, reply_payload})` during its
@@ -174,11 +179,6 @@ defmodule Phoenix.Socket do
   Custom channel implementations cannot be tested with `Phoenix.ChannelTest`
   and are currently considered experimental. The underlying API may be
   changed at any moment.
-
-  **Note:** in future Phoenix versions we will require custom channels
-  to provide a custom `child_spec/1` function instead of `start_link/1`.
-  Since the default behaviour of `child_spec/1` is to invoke `start_link/1`,
-  this behaviour should be backwards compatible in almost all cases.
   """
 
   require Logger
@@ -533,34 +533,15 @@ defmodule Phoenix.Socket do
   end
 
   defp user_connect(handler, endpoint, transport, serializer, params, connect_info) do
-    if pubsub_server = endpoint.config(:pubsub_server) do
-      # The information in the Phoenix.Socket goes to userland and channels.
-      socket = %Socket{
-        handler: handler,
-        endpoint: endpoint,
-        pubsub_server: pubsub_server,
-        serializer: serializer,
-        transport: transport
-      }
-
-      user_connect(handler, params, socket, connect_info)
-    else
-      Logger.error """
-      The :pubsub_server was not configured for endpoint #{inspect(endpoint)}.
-      Make sure to start a PubSub proccess in your application supervision tree:
-
-          {Phoenix.PubSub, [name: YOURAPP.PubSub, adapter: Phoenix.PubSub.PG2]}
-
-      And then list it your endpoint config:
-
-          pubsub_server: YOURAPP.PubSub
-      """
-
-      :error
-    end
-  end
+    # The information in the Phoenix.Socket goes to userland and channels.
+    socket = %Socket{
+      handler: handler,
+      endpoint: endpoint,
+      pubsub_server: endpoint.config(:pubsub_server),
+      serializer: serializer,
+      transport: transport
+    }
 
-  defp user_connect(handler, params, socket, connect_info) do
     # The information in the state is kept only inside the socket process.
     state = %{
       channels: %{},
diff --git a/mix.exs b/mix.exs
index aa779c889..e23a728c8 100644
--- a/mix.exs
+++ b/mix.exs
@@ -46,7 +46,7 @@ defmodule Phoenix.MixProject do
   def application do
     [
       mod: {Phoenix, []},
-      extra_applications: [:logger, :eex, :crypto],
+      extra_applications: [:logger, :eex, :crypto, :public_key],
       env: [
         logger: true,
         stacktrace_depth: nil,
@@ -73,7 +73,7 @@ defmodule Phoenix.MixProject do
       {:phoenix_html, "~> 2.14.2 or ~> 2.15", optional: true},
 
       # Docs dependencies
-      {:ex_doc, "~> 0.20", only: :docs},
+      {:ex_doc, "~> 0.22", only: :docs},
       {:inch_ex, "~> 0.2", only: :docs},
 
       # Test dependencies
diff --git a/mix.lock b/mix.lock
index f8ceb3aa3..75a08a8bd 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,12 +1,12 @@
 %{
   "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
   "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},
-  "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
-  "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"},
+  "earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"},
+  "ex_doc": {:hex, :ex_doc, "0.22.1", "9bb6d51508778193a4ea90fa16eac47f8b67934f33f8271d5e1edec2dc0eee4c", [:mix], [{:earmark, "~> 1.4.0", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "d957de1b75cb9f78d3ee17820733dc4460114d8b1e11f7ee4fd6546e69b1db60"},
   "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm", "d17ac3aa40835f7feb92913fcdf655edea59938fa8b25f5ce5d423e76a695c3b"},
   "inch_ex": {:hex, :inch_ex, "0.5.6", "418357418a553baa6d04eccd1b44171936817db61f4c0840112b420b8e378e67", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "7123ca0450686a61416a06cd38e26af18fd0f8c1cff5214770a957c6e0724338"},
   "jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b96c400e04b7b765c0854c05a4966323e90c0d11fee0483b1567cda079abb205"},
-  "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
+  "makeup": {:hex, :makeup, "1.0.1", "82f332e461dc6c79dbd82fbe2a9c10d48ed07146f0a478286e590c83c52010b5", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "49736fe5b66a08d8575bf5321d716bac5da20c8e6b97714fec2bcd6febcfa1f8"},
   "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
   "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
   "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
diff --git a/priv/templates/phx.gen.context/fixtures.ex b/priv/templates/phx.gen.context/fixtures.ex
new file mode 100644
index 000000000..e49a72e97
--- /dev/null
+++ b/priv/templates/phx.gen.context/fixtures.ex
@@ -0,0 +1,11 @@
+
+  def <%= schema.singular %>_fixture(attrs \\ %{}) do
+    {:ok, <%= schema.singular %>} =
+      attrs
+      |> Enum.into(%{
+<%= schema.params.create |> Enum.map(fn {key, val} -> "        #{key}: #{inspect(val)}" end) |> Enum.join(",\n") %>
+      })
+      |> <%= inspect context.module %>.create_<%= schema.singular %>()
+
+    <%= schema.singular %>
+  end
diff --git a/priv/templates/phx.gen.context/fixtures_module.ex b/priv/templates/phx.gen.context/fixtures_module.ex
new file mode 100644
index 000000000..111acbfee
--- /dev/null
+++ b/priv/templates/phx.gen.context/fixtures_module.ex
@@ -0,0 +1,6 @@
+defmodule <%= inspect context.module %>Fixtures do
+  @moduledoc """
+  This module defines test helpers for creating
+  entities via the `<%= inspect context.module %>` context.
+  """
+end
diff --git a/priv/templates/phx.gen.json/fallback_controller.ex b/priv/templates/phx.gen.json/fallback_controller.ex
index a4b88bf7a..6ae62aa42 100644
--- a/priv/templates/phx.gen.json/fallback_controller.ex
+++ b/priv/templates/phx.gen.json/fallback_controller.ex
@@ -6,14 +6,16 @@ defmodule <%= inspect context.web_module %>.FallbackController do
   """
   use <%= inspect context.web_module %>, :controller
 
-  <%= if schema.generate? do %>def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
+  <%= if schema.generate? do %># This clause handles errors returned by Ecto's insert/update/delete.
+  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
     conn
     |> put_status(:unprocessable_entity)
     |> put_view(<%= inspect context.web_module %>.ChangesetView)
     |> render("error.json", changeset: changeset)
   end
 
-  <% end %>def call(conn, {:error, :not_found}) do
+  <% end %># This clause is an example of how to handle resources that cannot be found.
+  def call(conn, {:error, :not_found}) do
     conn
     |> put_status(:not_found)
     |> put_view(<%= inspect context.web_module %>.ErrorView)


Test patch:
diff --git a/guides/testing/testing.md b/guides/testing/testing.md
index 3a1664d78..0d4f57511 100644
--- a/guides/testing/testing.md
+++ b/guides/testing/testing.md
@@ -102,12 +102,8 @@ defmodule HelloWeb.ConnCase do
   end
 
   setup tags do
-    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Demo.Repo)
-
-    unless tags[:async] do
-      Ecto.Adapters.SQL.Sandbox.mode(Demo.Repo, {:shared, self()})
-    end
-
+    pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Demo.Repo, shared: not tags[:async])
+    on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
     %{conn: Phoenix.ConnTest.build_conn()}
   end
 end
diff --git a/guides/testing/testing_channels.md b/guides/testing/testing_channels.md
index ac7b2f4cc..5e3cbd80f 100644
--- a/guides/testing/testing_channels.md
+++ b/guides/testing/testing_channels.md
@@ -50,12 +50,8 @@ defmodule HelloWeb.ChannelCase do
   end
 
   setup tags do
-    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Demo.Repo)
-
-    unless tags[:async] do
-      Ecto.Adapters.SQL.Sandbox.mode(Demo.Repo, {:shared, self()})
-    end
-
+    pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Demo.Repo, shared: not tags[:async])
+    on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
     :ok
   end
 end
diff --git a/guides/testing/testing_contexts.md b/guides/testing/testing_contexts.md
index 27da08323..5f76ccbfe 100644
--- a/guides/testing/testing_contexts.md
+++ b/guides/testing/testing_contexts.md
@@ -106,12 +106,8 @@ defmodule Hello.DataCase do
   end
 
   setup tags do
-    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Hello.Repo)
-
-    unless tags[:async] do
-      Ecto.Adapters.SQL.Sandbox.mode(Hello.Repo, {:shared, self()})
-    end
-
+    pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Demo.Repo, shared: not tags[:async])
+    on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
     :ok
   end
 
diff --git a/guides/testing/testing_controllers.md b/guides/testing/testing_controllers.md
index 965093f87..d3e813582 100644
--- a/guides/testing/testing_controllers.md
+++ b/guides/testing/testing_controllers.md
@@ -14,7 +14,7 @@ This gave us a number of modules for free, including a PostController and the as
 
 ## HTML controller tests
 
-If you open up "test/hello_web/controllers/post_controller.exs", you will find the following:
+If you open up "test/hello_web/controllers/post_controller_test.exs", you will find the following:
 
 ```elixir
 defmodule HelloWeb.PostControllerTest do
@@ -34,7 +34,7 @@ defmodule HelloWeb.PostControllerTest do
   ...
 ```
 
-Similar to the `PageControllerTest` that shops with our application, this controller tests uses `use HelloWeb.ConnCase` to setup the testing structure. Then, as usual, it defines some aliases, some module attributes to use throughout testing, and then it starts a series of `describe` blocks, each of them to test a different controller action.
+Similar to the `PageControllerTest` that ships with our application, this controller tests uses `use HelloWeb.ConnCase` to setup the testing structure. Then, as usual, it defines some aliases, some module attributes to use throughout testing, and then it starts a series of `describe` blocks, each of them to test a different controller action.
 
 ### The index action
 
@@ -47,11 +47,11 @@ def index(conn, _params) do
 end
 ```
 
-It gets all posts and renders the "index.html" template. The template can be found in "lib/hello_web/templates/page/index.html.eex".
+It gets all posts and renders the "index.html" template. The template can be found in `lib/hello_web/templates/page/index.html.eex`.
 
 The test looks like this:
 
-```elixi
+```elixir
 describe "index" do
   test "lists all posts", %{conn: conn} do
     conn = get(conn, Routes.post_path(conn, :index))
diff --git a/installer/templates/phx_test/support/channel_case.ex b/installer/templates/phx_test/support/channel_case.ex
index bdaf6dfb8..b802f7125 100644
--- a/installer/templates/phx_test/support/channel_case.ex
+++ b/installer/templates/phx_test/support/channel_case.ex
@@ -29,12 +29,7 @@ defmodule <%= web_namespace %>.ChannelCase do
   end<%= if ecto do %>
 
   setup tags do
-    <%= adapter_config[:test_setup] %>
-
-    unless tags[:async] do
-      <%= adapter_config[:test_async] %>
-    end
-
+<%= adapter_config[:test_setup] %>
     :ok
   end<% else %>
 
diff --git a/installer/templates/phx_test/support/conn_case.ex b/installer/templates/phx_test/support/conn_case.ex
index 72e9536b6..1ded92b8e 100644
--- a/installer/templates/phx_test/support/conn_case.ex
+++ b/installer/templates/phx_test/support/conn_case.ex
@@ -32,12 +32,7 @@ defmodule <%= web_namespace %>.ConnCase do
   end<%= if ecto do %>
 
   setup tags do
-    <%= adapter_config[:test_setup] %>
-
-    unless tags[:async] do
-      <%= adapter_config[:test_async] %>
-    end
-
+<%= adapter_config[:test_setup] %>
     {:ok, conn: Phoenix.ConnTest.build_conn()}
   end<% else %>
 
diff --git a/installer/test/phx_new_test.exs b/installer/test/phx_new_test.exs
index a5c7658e1..cfa5b0e75 100644
--- a/installer/test/phx_new_test.exs
+++ b/installer/test/phx_new_test.exs
@@ -502,9 +502,9 @@ defmodule Mix.Tasks.Phx.NewTest do
       assert_file "custom_path/config/prod.secret.exs", [~r/url: database_url/]
       assert_file "custom_path/lib/custom_path/repo.ex", "Ecto.Adapters.Postgres"
 
-      assert_file "custom_path/test/support/conn_case.ex", "Ecto.Adapters.SQL.Sandbox.checkout"
-      assert_file "custom_path/test/support/channel_case.ex", "Ecto.Adapters.SQL.Sandbox.checkout"
-      assert_file "custom_path/test/support/data_case.ex", "Ecto.Adapters.SQL.Sandbox.checkout"
+      assert_file "custom_path/test/support/conn_case.ex", "Ecto.Adapters.SQL.Sandbox.start_owner"
+      assert_file "custom_path/test/support/channel_case.ex", "Ecto.Adapters.SQL.Sandbox.start_owner"
+      assert_file "custom_path/test/support/data_case.ex", "Ecto.Adapters.SQL.Sandbox.start_owner"
     end
   end
 
@@ -519,9 +519,9 @@ defmodule Mix.Tasks.Phx.NewTest do
       assert_file "custom_path/config/prod.secret.exs", [~r/url: database_url/]
       assert_file "custom_path/lib/custom_path/repo.ex", "Ecto.Adapters.MyXQL"
 
-      assert_file "custom_path/test/support/conn_case.ex", "Ecto.Adapters.SQL.Sandbox.mode"
-      assert_file "custom_path/test/support/channel_case.ex", "Ecto.Adapters.SQL.Sandbox.mode"
-      assert_file "custom_path/test/support/data_case.ex", "Ecto.Adapters.SQL.Sandbox.mode"
+      assert_file "custom_path/test/support/conn_case.ex", "Ecto.Adapters.SQL.Sandbox.start_owner"
+      assert_file "custom_path/test/support/channel_case.ex", "Ecto.Adapters.SQL.Sandbox.start_owner"
+      assert_file "custom_path/test/support/data_case.ex", "Ecto.Adapters.SQL.Sandbox.start_owner"
     end
   end
 
@@ -536,9 +536,9 @@ defmodule Mix.Tasks.Phx.NewTest do
       assert_file "custom_path/config/prod.secret.exs", [~r/url: database_url/]
       assert_file "custom_path/lib/custom_path/repo.ex", "Ecto.Adapters.Tds"
 
-      assert_file "custom_path/test/support/conn_case.ex", "Ecto.Adapters.SQL.Sandbox.mode"
-      assert_file "custom_path/test/support/channel_case.ex", "Ecto.Adapters.SQL.Sandbox.mode"
-      assert_file "custom_path/test/support/data_case.ex", "Ecto.Adapters.SQL.Sandbox.mode"
+      assert_file "custom_path/test/support/conn_case.ex", "Ecto.Adapters.SQL.Sandbox.start_owner"
+      assert_file "custom_path/test/support/channel_case.ex", "Ecto.Adapters.SQL.Sandbox.start_owner"
+      assert_file "custom_path/test/support/data_case.ex", "Ecto.Adapters.SQL.Sandbox.start_owner"
     end
   end
 
diff --git a/installer/test/phx_new_umbrella_test.exs b/installer/test/phx_new_umbrella_test.exs
index 745d90b9d..f6cf230e0 100644
--- a/installer/test/phx_new_umbrella_test.exs
+++ b/installer/test/phx_new_umbrella_test.exs
@@ -496,8 +496,8 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       assert_file root_path(app, "config/test.exs"), [~r/username: "postgres"/, ~r/password: "postgres"/, ~r/hostname: "localhost"/]
       assert_file root_path(app, "config/prod.secret.exs"), [~r/url: database_url/]
 
-      assert_file web_path(app, "test/support/conn_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
-      assert_file web_path(app, "test/support/channel_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
+      assert_file web_path(app, "test/support/conn_case.ex"), "Ecto.Adapters.SQL.Sandbox.start_owner"
+      assert_file web_path(app, "test/support/channel_case.ex"), "Ecto.Adapters.SQL.Sandbox.start_owner"
     end
   end
 
@@ -514,8 +514,8 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       assert_file root_path(app, "config/test.exs"), [~r/username: "root"/, ~r/password: ""/]
       assert_file root_path(app, "config/prod.secret.exs"), [~r/url: database_url/]
 
-      assert_file web_path(app, "test/support/conn_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
-      assert_file web_path(app, "test/support/channel_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
+      assert_file web_path(app, "test/support/conn_case.ex"), "Ecto.Adapters.SQL.Sandbox.start_owner"
+      assert_file web_path(app, "test/support/channel_case.ex"), "Ecto.Adapters.SQL.Sandbox.start_owner"
     end
   end
 
@@ -532,8 +532,8 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       assert_file root_path(app, "config/test.exs"), [~r/username: "sa"/, ~r/password: "some!Password"/]
       assert_file root_path(app, "config/prod.secret.exs"), [~r/url: database_url/]
 
-      assert_file web_path(app, "test/support/conn_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
-      assert_file web_path(app, "test/support/channel_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
+      assert_file web_path(app, "test/support/conn_case.ex"), "Ecto.Adapters.SQL.Sandbox.start_owner"
+      assert_file web_path(app, "test/support/channel_case.ex"), "Ecto.Adapters.SQL.Sandbox.start_owner"
     end
   end
 
diff --git a/priv/templates/phx.gen.context/test_cases.exs b/priv/templates/phx.gen.context/test_cases.exs
index 92f73323f..cab316929 100644
--- a/priv/templates/phx.gen.context/test_cases.exs
+++ b/priv/templates/phx.gen.context/test_cases.exs
@@ -2,18 +2,9 @@
   describe "<%= schema.plural %>" do
     alias <%= inspect schema.module %>
 
-    @valid_attrs <%= inspect schema.params.create %>
-    @update_attrs <%= inspect schema.params.update %>
-    @invalid_attrs <%= inspect for {key, _} <- schema.params.create, into: %{}, do: {key, nil} %>
-
-    def <%= schema.singular %>_fixture(attrs \\ %{}) do
-      {:ok, <%= schema.singular %>} =
-        attrs
-        |> Enum.into(@valid_attrs)
-        |> <%= inspect context.alias %>.create_<%= schema.singular %>()
+    import <%= inspect context.module %>Fixtures
 
-      <%= schema.singular %>
-    end
+    @invalid_attrs <%= inspect for {key, _} <- schema.params.create, into: %{}, do: {key, nil} %>
 
     test "list_<%= schema.plural %>/0 returns all <%= schema.plural %>" do
       <%= schema.singular %> = <%= schema.singular %>_fixture()
@@ -26,7 +17,9 @@
     end
 
     test "create_<%= schema.singular %>/1 with valid data creates a <%= schema.singular %>" do
-      assert {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} = <%= inspect context.alias %>.create_<%= schema.singular %>(@valid_attrs)<%= for {field, value} <- schema.params.create do %>
+      valid_attrs = <%= inspect schema.params.create %>
+
+      assert {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} = <%= inspect context.alias %>.create_<%= schema.singular %>(valid_attrs)<%= for {field, value} <- schema.params.create do %>
       assert <%= schema.singular %>.<%= field %> == <%= Mix.Phoenix.Schema.value(schema, field, value) %><% end %>
     end
 
@@ -36,7 +29,9 @@
 
     test "update_<%= schema.singular %>/2 with valid data updates the <%= schema.singular %>" do
       <%= schema.singular %> = <%= schema.singular %>_fixture()
-      assert {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} = <%= inspect context.alias %>.update_<%= schema.singular %>(<%= schema.singular %>, @update_attrs)<%= for {field, value} <- schema.params.update do %>
+      update_attrs = <%= inspect schema.params.update %>
+
+      assert {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} = <%= inspect context.alias %>.update_<%= schema.singular %>(<%= schema.singular %>, update_attrs)<%= for {field, value} <- schema.params.update do %>
       assert <%= schema.singular %>.<%= field %> == <%= Mix.Phoenix.Schema.value(schema, field, value) %><% end %>
     end
 
diff --git a/priv/templates/phx.gen.html/controller_test.exs b/priv/templates/phx.gen.html/controller_test.exs
index 187a869ef..35c76c0fe 100644
--- a/priv/templates/phx.gen.html/controller_test.exs
+++ b/priv/templates/phx.gen.html/controller_test.exs
@@ -1,17 +1,12 @@
 defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>ControllerTest do
   use <%= inspect context.web_module %>.ConnCase
 
-  alias <%= inspect context.module %>
+  import <%= inspect context.module %>Fixtures
 
   @create_attrs <%= inspect schema.params.create %>
   @update_attrs <%= inspect schema.params.update %>
   @invalid_attrs <%= inspect for {key, _} <- schema.params.create, into: %{}, do: {key, nil} %>
 
-  def fixture(:<%= schema.singular %>) do
-    {:ok, <%= schema.singular %>} = <%= inspect context.alias %>.create_<%= schema.singular %>(@create_attrs)
-    <%= schema.singular %>
-  end
-
   describe "index" do
     test "lists all <%= schema.plural %>", %{conn: conn} do
       conn = get(conn, Routes.<%= schema.route_helper %>_path(conn, :index))
@@ -83,7 +78,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   end
 
   defp create_<%= schema.singular %>(_) do
-    <%= schema.singular %> = fixture(:<%= schema.singular %>)
+    <%= schema.singular %> = <%= schema.singular %>_fixture()
     %{<%= schema.singular %>: <%= schema.singular %>}
   end
 end
diff --git a/priv/templates/phx.gen.json/controller_test.exs b/priv/templates/phx.gen.json/controller_test.exs
index 502787187..9f99e08e8 100644
--- a/priv/templates/phx.gen.json/controller_test.exs
+++ b/priv/templates/phx.gen.json/controller_test.exs
@@ -1,7 +1,8 @@
 defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>ControllerTest do
   use <%= inspect context.web_module %>.ConnCase
 
-  alias <%= inspect context.module %>
+  import <%= inspect context.module %>Fixtures
+
   alias <%= inspect schema.module %>
 
   @create_attrs %{
@@ -12,11 +13,6 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   }
   @invalid_attrs <%= inspect for {key, _} <- schema.params.create, into: %{}, do: {key, nil} %>
 
-  def fixture(:<%= schema.singular %>) do
-    {:ok, <%= schema.singular %>} = <%= inspect context.alias %>.create_<%= schema.singular %>(@create_attrs)
-    <%= schema.singular %>
-  end
-
   setup %{conn: conn} do
     {:ok, conn: put_req_header(conn, "accept", "application/json")}
   end
@@ -82,7 +78,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   end
 
   defp create_<%= schema.singular %>(_) do
-    <%= schema.singular %> = fixture(:<%= schema.singular %>)
+    <%= schema.singular %> = <%= schema.singular %>_fixture()
     %{<%= schema.singular %>: <%= schema.singular %>}
   end
 end
diff --git a/priv/templates/phx.gen.live/live_test.exs b/priv/templates/phx.gen.live/live_test.exs
index 9fccb0c48..2c5609e0a 100644
--- a/priv/templates/phx.gen.live/live_test.exs
+++ b/priv/templates/phx.gen.live/live_test.exs
@@ -2,20 +2,14 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
   use <%= inspect context.web_module %>.ConnCase
 
   import Phoenix.LiveViewTest
-
-  alias <%= inspect context.module %>
+  import <%= inspect context.module %>Fixtures
 
   @create_attrs <%= inspect schema.params.create %>
   @update_attrs <%= inspect schema.params.update %>
   @invalid_attrs <%= inspect for {key, _} <- schema.params.create, into: %{}, do: {key, nil} %>
 
-  defp fixture(:<%= schema.singular %>) do
-    {:ok, <%= schema.singular %>} = <%= inspect context.alias %>.create_<%= schema.singular %>(@create_attrs)
-    <%= schema.singular %>
-  end
-
   defp create_<%= schema.singular %>(_) do
-    <%= schema.singular %> = fixture(:<%= schema.singular %>)
+    <%= schema.singular %> = <%= schema.singular %>_fixture()
     %{<%= schema.singular %>: <%= schema.singular %>}
   end
 
diff --git a/test/fixtures/digest/priv/static/precompressed.js.br b/test/fixtures/digest/priv/static/precompressed.js.br
new file mode 100644
index 000000000..5a090955f
--- /dev/null
+++ b/test/fixtures/digest/priv/static/precompressed.js.br
@@ -0,0 +1,1 @@
+Brotli
diff --git a/test/fixtures/digest/priv/static/precompressed.js.gz b/test/fixtures/digest/priv/static/precompressed.js.gz
new file mode 100644
index 000000000..d234d64b1
--- /dev/null
+++ b/test/fixtures/digest/priv/static/precompressed.js.gz
@@ -0,0 +1,1 @@
+gzip
diff --git a/test/mix/tasks/phx.gen.context_test.exs b/test/mix/tasks/phx.gen.context_test.exs
index b0ba398db..0e965bd5f 100644
--- a/test/mix/tasks/phx.gen.context_test.exs
+++ b/test/mix/tasks/phx.gen.context_test.exs
@@ -37,6 +37,7 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
       assert String.ends_with?(context.dir, "lib/phoenix/blog")
       assert String.ends_with?(context.file, "lib/phoenix/blog.ex")
       assert String.ends_with?(context.test_file, "test/phoenix/blog_test.exs")
+      assert String.ends_with?(context.test_fixtures_file, "test/support/fixtures/blog_fixtures.ex")
       assert String.ends_with?(context.schema.file, "lib/phoenix/blog/post.ex")
     end
   end
@@ -64,6 +65,7 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
       assert String.ends_with?(context.dir, "lib/phoenix/site/blog")
       assert String.ends_with?(context.file, "lib/phoenix/site/blog.ex")
       assert String.ends_with?(context.test_file, "test/phoenix/site/blog_test.exs")
+      assert String.ends_with?(context.test_fixtures_file, "test/support/fixtures/site/blog_fixtures.ex")
       assert String.ends_with?(context.schema.file, "lib/phoenix/site/blog/post.ex")
     end
   end
@@ -80,6 +82,7 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
       context = Context.new("Blog", schema, [])
       assert Context.pre_existing?(context)
       refute Context.pre_existing_tests?(context)
+      refute Context.pre_existing_test_fixtures?(context)
 
       File.mkdir_p!("test/phoenix/blog")
       File.write!(context.test_file, """
@@ -87,6 +90,13 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
       end
       """)
       assert Context.pre_existing_tests?(context)
+
+      File.mkdir_p!("test/support/fixtures")
+      File.write!(context.test_fixtures_file, """
+      defmodule Phoenix.BlogFixtures do
+      end
+      """)
+      assert Context.pre_existing_test_fixtures?(context)
     end
   end
 
@@ -142,7 +152,13 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
       assert_file "test/phoenix/blog_test.exs", fn file ->
         assert file =~ "use Phoenix.DataCase"
         assert file =~ "describe \"posts\" do"
+        assert file =~ "import Phoenix.BlogFixtures"
+      end
+
+      assert_file "test/support/fixtures/blog_fixtures.ex", fn file ->
+        assert file =~ "defmodule Phoenix.BlogFixtures do"
         assert file =~ "def post_fixture(attrs \\\\ %{})"
+        assert file =~ "title: \"some title\""
       end
 
       assert [path] = Path.wildcard("priv/repo/migrations/*_create_posts.exs")
@@ -167,7 +183,13 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
       assert_file "test/phoenix/blog_test.exs", fn file ->
         assert file =~ "use Phoenix.DataCase"
         assert file =~ "describe \"comments\" do"
+        assert file =~ "import Phoenix.BlogFixtures"
+      end
+
+      assert_file "test/support/fixtures/blog_fixtures.ex", fn file ->
+        assert file =~ "defmodule Phoenix.BlogFixtures do"
         assert file =~ "def comment_fixture(attrs \\\\ %{})"
+        assert file =~ "title: \"some title\""
       end
 
       assert [path] = Path.wildcard("priv/repo/migrations/*_create_comments.exs")
@@ -206,7 +228,13 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
       assert_file "test/phoenix/blog_test.exs", fn file ->
         assert file =~ "use Phoenix.DataCase"
         assert file =~ "describe \"posts\" do"
+        assert file =~ "import Phoenix.BlogFixtures"
+      end
+
+      assert_file "test/support/fixtures/blog_fixtures.ex", fn file ->
+        assert file =~ "defmodule Phoenix.BlogFixtures do"
         assert file =~ "def post_fixture(attrs \\\\ %{})"
+        assert file =~ "title: \"some title\""
       end
 
       assert Path.wildcard("priv/repo/migrations/*_create_posts.exs") == []
diff --git a/test/phoenix/digester_test.exs b/test/phoenix/digester_test.exs
index 03b25c2ab..37df1b8d6 100644
--- a/test/phoenix/digester_test.exs
+++ b/test/phoenix/digester_test.exs
@@ -84,6 +84,19 @@ defmodule Phoenix.DigesterTest do
                "93pY5dBa8nHHi0Zfj75O/vXCBXb+UvEVCyU7Yd3pzOJ7o1wkYBWbvs3pVXhBChEmo8MDANT11vsggo2+bnYqoQ=="
     end
 
+    test "excludes compiled files" do
+      input_path = "test/fixtures/digest/priv/static/"
+      assert :ok = Phoenix.Digester.compile(input_path, @output_path)
+      output_files = assets_files(@output_path)
+
+      json = Path.join(@output_path, "cache_manifest.json") |> json_read!()
+      refute json["latest"]["precompressed.js.gz"]
+      refute json["latest"]["precompressed.js.br"]
+
+      assert :ok = Phoenix.Digester.compile(@output_path, @output_path)
+      assert output_files == assets_files(@output_path)
+    end
+
     test "old versions maintain their mtime" do
       source_path = "test/fixtures/digest/priv/static/"
       input_path = "tmp/digest/static"
diff --git a/test/phoenix/endpoint/endpoint_test.exs b/test/phoenix/endpoint/endpoint_test.exs
index 26d384685..a70af1bd3 100644
--- a/test/phoenix/endpoint/endpoint_test.exs
+++ b/test/phoenix/endpoint/endpoint_test.exs
@@ -10,6 +10,7 @@ defmodule Phoenix.Endpoint.EndpointTest do
            force_ssl: [subdomains: true],
            cache_static_manifest: "../../../../test/fixtures/digest/compile/cache_manifest.json",
            pubsub_server: :endpoint_pub]
+
   Application.put_env(:phoenix, __MODULE__.Endpoint, @config)
 
   defmodule Endpoint do
diff --git a/test/phoenix/integration/endpoint_test.exs b/test/phoenix/integration/endpoint_test.exs
index 42b1a4d59..d5dbd1f7d 100644
--- a/test/phoenix/integration/endpoint_test.exs
+++ b/test/phoenix/integration/endpoint_test.exs
@@ -161,7 +161,7 @@ defmodule Phoenix.Integration.EndpointTest do
       assert_receive {:event, [:plug_adapter, :call, :stop], %{duration: _},
                       %{
                         adapter: :phoenix_cowboy,
-                        conn: %{request_path: "/"},
+                        conn: %{request_path: "/", status: 200},
                         plug: ProdEndpoint
                       }}
 
diff --git a/test/phoenix/integration/websocket_channels_test.exs b/test/phoenix/integration/websocket_channels_test.exs
index 217f142f9..fdc0699a4 100644
--- a/test/phoenix/integration/websocket_channels_test.exs
+++ b/test/phoenix/integration/websocket_channels_test.exs
@@ -64,20 +64,24 @@ defmodule Phoenix.Integration.WebSocketChannelsTest do
   defmodule CustomChannel do
     use GenServer, restart: :temporary
 
-    def start_link(triplet) do
-      GenServer.start_link(__MODULE__, triplet)
+    def start_link(from) do
+      GenServer.start_link(__MODULE__, from)
     end
 
-    def init({payload, from, socket}) do
+    def init({_, _}) do
+      {:ok, :init}
+    end
+
+    def handle_info({Phoenix.Channel, payload, from, socket}, :init) do
       case payload["action"] do
         "ok" ->
           GenServer.reply(from, {:ok, %{"action" => "ok"}})
-          {:ok, socket}
+          {:noreply, socket}
 
         "ignore" ->
           GenServer.reply(from, {:error, %{"action" => "ignore"}})
           send(self(), :stop)
-          {:ok, socket}
+          {:noreply, socket}
 
         "error" ->
           raise "oops"