
Pruébalo : Phoenix-Chat .fly.dev
¡Un tutorial paso a paso para construir, probar e implementar una aplicación de chat en Phoenix!
page_controller_test.exsuser_socket.jsexcoveralls como dependencia (de desarrollo) para mix.exscoveralls.jsonPresence a rastrear quién está en línea Las aplicaciones de chat son el "Hello World" de ejemplos en tiempo real.
Lamentablemente, la mayoría de las aplicaciones de ejemplo muestran algunos conceptos básicos y luego ignoran el resto ...? ♀️
¡Entonces los principiantes a menudo se quedan perdidos o confundidos en cuanto a lo que deben hacer o aprender a continuación !
Muy pocos tutoriales consideran las pruebas, la implementación, la documentación u otras " mejoras " que forman parte del " mundo real " de la construcción y ejecución de aplicaciones; Entonces, esos son temas que cubriremos para " llenar los vacíos ".
Escribimos este tutorial para que sea la forma más fácil de aprender Phoenix , Ecto y Channels con un ejemplo práctico que cualquiera puede seguir .
Este es el ejemplo/tutorial que deseamos que tuviéramos cuando estábamos aprendiendo Elixir , Phoenix ... si lo encuentras útil, ¡gracias!
Un simple tutorial paso a paso que le muestra cómo:
mix phx.new chat "Generator) )Fly.io Para que puedas mostrarle a la gente tu creación!Inicialmente , deliberadamente omitimos los archivos de configuración y " Phoenix -Terals " porque ustedes ( principiantes ) no necesitan saber sobre ellos para comenzar . Pero no se preocupe, volveremos a ellos cuando sea necesario . Favorecemos el " justo en el tiempo " ( cuando lo necesitas ) el aprendizaje, ya que es inmediatamente obvio y práctico por qué estamos aprendiendo algo.
Este ejemplo es para principiantes completos como una aplicación " My First Phoenix ".
Tratamos de asumir lo menos posible, pero si cree que " omitimos un paso " o se siente " atrapado " por cualquier motivo, o tiene alguna pregunta ( relacionada con este ejemplo ), ¡abra un problema en Github!
Tanto las comunidades @DWYL y Phoenix son súper para principiantes , así que no tengas miedo/tímido.
Además, al hacer preguntas, ¡está ayudando a todos los que están o pueden estar atrapados con lo mismo !
Estas instrucciones le muestran cómo crear la aplicación de chat desde cero .
brew install elixirNota : Si ya tiene
Elixirinstalado en su Mac, y solo desea actualizar a la última versión, ejecute:brew upgrade elixir
mix archive.install hex phx_new El conocimiento básico de sintaxis de elixir ayudará,
Consulte: DWYL/ Learn-Elixir
El conocimiento básico de JavaScript es ventajoso ( pero no esencial ya que el código "front-end" es bastante básico y bien comentado ). Ver: DWYL/JavaScript-the Good-Parts-Notas
Verifique que tenga la última versión de Elixir ( ejecute el siguiente comando en su terminal ):
elixir -vDeberías ver algo como:
Erlang/OTP 25 [erts-13.1.1] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
Elixir 1.14.1 (compiled with Erlang/OTP 25)Verifique que tenga la última versión de Phoenix :
mix phx.new -vDeberías ver:
Phoenix installer v1.7.0-rc.2Nota : Si su versión
Phoenixes más nueva , ¡no dude en actualizar este documento! Hacemos todo lo posible para mantenerlo actualizado ... ¡pero sus contribuciones siempre son bienvenidas!
En este tutorial, estamos utilizando Phoenix 1.7-RC2, el segundo candidato de lanzamiento para
Phoenix 1.7. Al momento de escribir, si instala Phoenix, la última versión estable no esv1.7. Para usar esta versión, siga la guía oficial (¡no se preocupe, solo está ejecutando un comando!)-> https://www.phoenixframework.org/blog/phoenix-1.7-relacionadoSin embargo, si está leyendo esto después de su lanzamiento,
v1.7se instalará para usted, y debería verPhoenix installer v1.7.0en su terminal.
Confirmar que PostgreSQL se está ejecutando ( por lo que la aplicación puede almacenar mensajes de chat ) Ejecute el siguiente comando:
lsof -i :5432Debería ver la salida similar a la siguiente:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
postgres 529 Nelson 5u IPv6 0xbc5d729e529f062b 0t0 TCP localhost:postgresql (LISTEN)
postgres 529 Nelson 6u IPv4 0xbc5d729e55a89a13 0t0 TCP localhost:postgresql (LISTEN) Esto nos dice que PostgreSQL está " escuchando " en el puerto TCP 5432 ( el puerto predeterminado )
Si el comando lsof no produce ningún resultado en su terminal, ejecute:
pg_isreadyDebería imprimir lo siguiente:
/tmp:5432 - accepting connectionsCon todos esos "cheques previos al vuelo" realizados, ¡ volemos !
Antes de intentar construir la aplicación de chat desde cero, clon y ejecute la versión de trabajo terminada para tener una idea de qué esperar.
En su terminal ejecuta el siguiente comando para clonar el repositorio:
git clone [email protected]:dwyl/phoenix-chat-example.git Cambie en el directorio phoenix-chat-example e instale las dependencias Elixir y Node.js con este comando:
cd phoenix-chat-example
mix setupEjecute la aplicación Phoenix con el comando:
mix phx.serverSi abre la aplicación Localhost: 4000 en dos navegadores web más, puede ver los mensajes de chat que se muestran en todos ellos tan pronto como presione la tecla ENTER :

Ahora que ha confirmado que la aplicación de chat de Phoenix terminada funciona en su máquina, ¡es hora de construirla desde cero!
Directorio de cambio:
cd ..¡Y comienza a construir!
En su programa terminal en su localhost, escriba el siguiente comando para crear la aplicación:
mix phx.new chat --no-mailer --no-dashboard --no-gettext Que creará la estructura del directorio y los archivos del proyecto.
Estamos ejecutando el comando
mix phx.newcon el--no-mailer--no-dashboard--no-gettextargumentos porque no queremos que nuestro proyecto genere archivos de correo, incluya unPhoenix.LiveDashboardy genere archivosgettext(parai18n).
Cuando se le pide que " obtenga e instale dependencias ? [Yn]",
Escriba Y en su terminal, seguido de la clave Enter ( return ).
Deberías ver: 
Cambie el directorio en el directorio chat ejecutando el comando sugerido:
cd chatAhora ejecute el siguiente comando:
mix setupNota : En este punto ya hay una "aplicación", simplemente no hace nada (todavía) ...
Puede ejecutarmix phx.serveren su terminal: no se preocupe si está viendo un error
Mensajes, esto se debe a que aún no hemos creado nuestra base de datos.
¡Nos encargaremos de eso en el paso 6!
Por ahora, abra http: // localhost: 4000 en su navegador
Y verá la página de iniciodefault"Bienvenido a Phoenix":

Apague el servidor Phoenix en su terminal con el comando CTRL + C.
En la ventana de su terminal, ejecute el siguiente comando:
mix test
Debería ver la salida similar a la siguiente:
Generated chat app
.....
Finished in 0.02 seconds (0.02s async, 0.00s sync)
5 tests, 0 failures
Randomized with seed 84184Ahora que hemos confirmado que todo funciona (todas las pruebas pasan), ¡continuemos a la parte interesante !
Genere el canal (WebSocket) que se utilizará en la aplicación de chat:
mix phx.gen.channel RoomSi se le solicita que confirme la instalación de un nuevo controlador de socket tipo
yy presione la tecla[Enter].
Esto creará tres archivos :
* creating lib/chat_web/channels/room_channel.ex
* creating test/chat_web/channels/room_channel_test.exs
* creating test/support/channel_case.exAdemás de crear dos archivos más :
* creating lib/chat_web/channels/user_socket.ex
* creating assets/js/user_socket.js El archivo room_channel.ex maneja los mensajes de recepción/envío y el room_channel_test.exs prueba la interacción básica con el canal. Nos centraremos en los archivos socket creados después. ( ¡No se preocupe por esto todavía, veremos el archivo de prueba en el paso 14 a continuación !)
Se nos informa que necesitamos actualizar un código en nuestra aplicación:
Add the socket handler to your ` lib/chat_web/endpoint.ex ` , for example:
socket " /socket " , ChatWeb.UserSocket,
websocket: true,
longpoll: false
For the front-end integration, you need to import the ` user_socket.js `
in your ` assets/js/app.js ` file:
import " ./user_socket.js " El generador nos pide que importemos el código del cliente en la interfaz. Hagamos eso más tarde. Por ahora, abra el archivo lib/chat_web/endpoint.ex y siga las instrucciones.
Después de esto, abra el archivo llamado /lib/chat_web/channels/user_socket.ex
y cambiar la línea:
channel "room:*" , ChatWeb.RoomChannela:
channel "room:lobby" , ChatWeb.RoomChannelRevise el cambio aquí.
Esto asegurará que cualquier mensaje que se envíe a "room:lobby" se enruten a nuestra RoomChannel .
La "room.* anterior significaba que se enrutó cualquier subtópico dentro de "room" . Pero por ahora, ¡reducimos solo a un subtópico?
Para obtener más detalles sobre los canales de Phoenix, ( le recomendamos encarecidamente ) leer: https://hexdocs.pm/phoenix/channels.html
Abra el archivo /lib/chat_web/controllers/page_html/home.html.heex
y copiar pete ( o escriba ) el siguiente código:
<!-- The list of messages will appear here: -->
< div class =" mt-[4rem] " >
< ul id =" msg-list " phx-update =" append " class =" pa-1 " > </ ul >
</ div >
< footer class =" bg-slate-800 p-2 h-[3rem] fixed bottom-0 w-full flex justify-center " >
< div class =" w-full flex flex-row items-center text-gray-700 focus:outline-none font-normal " >
< input type =" text " id =" name " placeholder =" Name " required
class =" grow-0 w-1/6 px-1.5 py-1.5 " />
< input type =" text " id =" msg " placeholder =" Your message " required
class =" grow w-2/3 mx-1 px-2 py-1.5 " />
< button id =" send " class =" text-white bold rounded px-3 py-1.5 w-fit
transition-colors duration-150 bg-sky-500 hover:bg-sky-600 " >
Send
</ button >
</ div >
</ footer > Este es el formulario básico que usaremos para ingresar mensajes de chat.
Las clases, por ejemplo w-full y items-center son clases TailwindCSS para diseñar el formulario.
¡Phoenix incluye el viento de cola de forma predeterminada para que pueda avanzar con su aplicación/idea/"MVP"!
Si eres nuevo en
Tailwind, consulte: DWYL/ Learn-TailwindSi tiene preguntas sobre cualquiera de las clases
Tailwindutilizadas, gaste 2 minutos en Google o buscando en los documentos oficiales (¡excelentes!)
Su archivo de plantilla home.html.heex debe verse así: /lib/chat_web/controllers/page_html/home.html.heex
Abra el archivo lib/chat_web/components/layouts/root.html.heex y localice la etiqueta <body> . Reemplace el contenido del <body> con el siguiente código:
< body class =" bg-white antialiased min-h-screen flex flex-col " >
< header class =" bg-slate-800 w-full h-[4rem] top-0 fixed flex flex-col justify-center z-10 " >
< div class =" flex flex-row justify-center items-center " >
< h1 class =" w-4/5 md:text-3xl text-center font-mono text-white " >
Phoenix Chat Example
</ h1 >
</ div >
</ header >
< %= @inner_content % >
</ body > Su archivo de plantilla root.html.heex debe verse así: /lib/chat_web/components/layouts/root.html.heex
Al final de este paso, si ejecuta el servidor Phoenix mix phx.server , y ver la aplicación en su navegador, se verá así:

Por lo tanto, ya está empezando a parecer una aplicación de chat básica. Lamentablemente, ya que cambiamos la copia del home.html.heex nuestro page_controller_test.exs ahora falla:
Ejecute el comando:
mix test 1) test GET / (ChatWeb.PageControllerTest)
test/chat_web/controllers/page_controller_test.exs:4
Assertion with =~ failed
code: assert html_response(conn, 200) =~ "Peace of mind from prototype to production"
Afortunadamente, esto es fácil de solucionar.
page_controller_test.exs Abra el archivo test/chat_web/controllers/page_controller_test.exs y reemplace la línea:
assert html_response ( conn , 200 ) =~ "Peace of mind from prototype to production"Con:
assert html_response ( conn , 200 ) =~ "Phoenix Chat Example"Ahora, si vuelve a ejecutar las pruebas, pasarán:
mix test
Salida de muestra:
........
Finished in 0.1 seconds (0.09s async, 0.06s sync)
8 tests, 0 failures
Randomized with seed 275786
Abra assets/js/app.js , descomplie y cambie la línea:
import socket from "./user_socket.js" Con la línea sin commentada , nuestra aplicación importará el archivo socket.js que nos dará la funcionalidad de WebSocket.
Luego agregue el siguiente código JavaScript ("Cliente") a la parte inferior del archivo:
/* Message list code */
const ul = document . getElementById ( 'msg-list' ) ; // list of messages.
const name = document . getElementById ( 'name' ) ; // name of message sender
const msg = document . getElementById ( 'msg' ) ; // message input field
const send = document . getElementById ( 'send' ) ; // send button
const channel = socket . channel ( 'room:lobby' , { } ) ; // connect to chat "room"
channel . join ( ) ; // join the channel.
// Listening to 'shout' events
channel . on ( 'shout' , function ( payload ) {
render_message ( payload )
} ) ;
// Send the message to the server on "shout" channel
function sendMessage ( ) {
channel . push ( 'shout' , {
name : name . value || "guest" , // get value of "name" of person sending the message. Set guest as default
message : msg . value , // get message text (value) from msg input field.
inserted_at : new Date ( ) // date + time of when the message was sent
} ) ;
msg . value = '' ; // reset the message input field for next message.
window . scrollTo ( 0 , document . documentElement . scrollHeight ) // scroll to the end of the page on send
}
// Render the message with Tailwind styles
function render_message ( payload ) {
const li = document . createElement ( "li" ) ; // create new list item DOM element
// Message HTML with Tailwind CSS Classes for layout/style:
li . innerHTML = `
<div class="flex flex-row w-[95%] mx-2 border-b-[1px] border-slate-300 py-2">
<div class="text-left w-1/5 font-semibold text-slate-800 break-words">
${ payload . name }
<div class="text-xs mr-1">
<span class="font-thin"> ${ formatDate ( payload . inserted_at ) } </span>
<span> ${ formatTime ( payload . inserted_at ) } </span>
</div>
</div>
<div class="flex w-3/5 mx-1 grow">
${ payload . message }
</div>
</div>
`
// Append to list
ul . appendChild ( li ) ;
}
// Listen for the [Enter] keypress event to send a message:
msg . addEventListener ( 'keypress' , function ( event ) {
if ( event . key === `Enter` && msg . value . length > 0 ) { // don't sent empty msg.
sendMessage ( )
}
} ) ;
// On "Send" button press
send . addEventListener ( 'click' , function ( event ) {
if ( msg . value . length > 0 ) { // don't sent empty msg.
sendMessage ( )
}
} ) ;
// Date formatting
function formatDate ( datetime ) {
const m = new Date ( datetime ) ;
return m . getUTCFullYear ( ) + "/"
+ ( "0" + ( m . getUTCMonth ( ) + 1 ) ) . slice ( - 2 ) + "/"
+ ( "0" + m . getUTCDate ( ) ) . slice ( - 2 ) ;
}
// Time formatting
function formatTime ( datetime ) {
const m = new Date ( datetime ) ;
return ( "0" + m . getUTCHours ( ) ) . slice ( - 2 ) + ":"
+ ( "0" + m . getUTCMinutes ( ) ) . slice ( - 2 ) + ":"
+ ( "0" + m . getUTCSeconds ( ) ) . slice ( - 2 ) ;
}Tómese un momento para leer el código JavaScript y confirmar su comprensión de lo que está haciendo.
Esperemos que los comentarios en línea se expliquen por sí mismos, pero si algo no está claro, ¡pregunte!
En este punto, su archivo app.js debe verse así: /assets/js/app.js
user_socket.js Por defecto, el canal Phoenix (cliente) se suscribirá a la habitación genérica: "topic:subtopic" . Como no vamos a usar esto, podemos evitar ver ningún error de "unable to join: unmatched topic" en nuestro navegador/consola simplemente comentando algunas líneas en el archivo user_socket.js . Abra el archivo en su editor y localice las siguientes líneas:
let channel = socket . channel ( "room:42" , { } )
channel . join ( )
. receive ( "ok" , resp => { console . log ( "Joined successfully" , resp ) } )
. receive ( "error" , resp => { console . log ( "Unable to join" , resp ) } )Comenta las líneas para que no se ejecutarán:
//let channel = socket.channel("room:42", {})
//channel.join()
// .receive("ok", resp => { console.log("Joined successfully", resp) })
// .receive("error", resp => { console.log("Unable to join", resp) }) Su user_socket.js ahora debería verse así: /assets/js/user_socket.js
Si luego decide ordenar su aplicación de chat, puede
deleteestas líneas comentadas del archivo.
Solo los mantenemos como referencia a cómo unir canales y recibir mensajes.
Si está ejecutando la aplicación, intente llenar el name y los campos message y haga clic en Enter (o presione Send ).
¡El mensaje debería aparecer en diferentes ventanas!

Con esto hecho, podemos continuar.
Si no queríamos guardar el historial de chat, podríamos implementar esta aplicación de inmediato y terminaríamos.
De hecho, podría ser una " función de uso "/" característica " para tener un chat " efímero " sin ningún historial ... ver: http://www.psstchat.com/.
Pero asumimos que la mayoría de las aplicaciones de chat guarda historia para que las
newpersonas que se unan al "canal" puedan ver la historia y las personas que están "ausentes" brevemente "pueden" ponerse al día "en la historia.
Ejecute el siguiente comando en su terminal:
mix phx.gen.schema Message messages name:string message:stringDebería ver la siguiente salida:
* creating lib/chat/message.ex
* creating priv/repo/migrations/20230203114114_create_messages.exs
Remember to update your repository by running migrations:
$ mix ecto.migrateDesglosemos ese comando para mayor claridad:
mix phx.gen.schema : el comando mezclar para crear un nuevo esquema (tabla de base de datos)Message : el nombre singular para el registro en nuestra "colección" de mensajes "messages : el nombre de la colección ( o tabla de base de datos )name:string : el nombre de la persona que envía un mensaje, almacenado como una string .message:string : el mensaje enviado por la persona, también almacenado como una string . La línea creating lib/chat/message.ex crea el "esquema" para nuestra tabla de base de datos de mensajes.
Además, se crea un archivo de migración, por ejemplo: creating priv/repo/migrations/20230203114114_create_messages.exs La " migración " en realidad crea la tabla de la base de datos en nuestra base de datos.
En su terminal ejecuta el siguiente comando para crear la tabla messages :
mix ecto.migratePara el contexto recomendamos leer: hexdocs.pm/ecto_sql/ ecto.migration .html
Debería ver lo siguiente en su terminal:
11:42:10.130 [info] == Running 20230203114114 Chat.Repo.Migrations.CreateMessages.change/0 forward
11:42:10.137 [info] create table messages
11:42:10.144 [info] == Migrated 20230203114114 in 0.0s Si abre su GUI PostgreSQL ( por ejemplo: pgadmin ) verá que la tabla de mensajes se ha creado en la base de datos chat_dev :

Puede ver el esquema de la tabla " haciendo clic derecho " ( ctrl + click en Mac ) en la tabla messages y seleccionando "Propiedades":

Nota : Para las secciones 7, 8 y 9, desarrollaremos cómo nuestro código "maneja" los diferentes eventos que pueden ocurrir en nuestra aplicación de chat.
Phoenix abstrae gran parte de la lógica subyacente de pasos de mensajes en la comunicación de procesos de Elixir (para obtener más información sobre cómo se comunican los procesos de elixir, lea aquí).
En Phoenix, los eventos/mensajes enviados desde el cliente se enrutan automáticamente a las funciones del controlador correspondiente en función del nombre del evento, haciendo que el manejo de mensajes sea sin problemas y directos.
Abra el archivo lib/chat_web/channels/room_channel.ex y dentro de la función def handle_in("shout", payload, socket) do Agregue la siguiente línea:
Chat.Message . changeset ( % Chat.Message { } , payload ) |> Chat.Repo . insert Para que su función termine luciendo así:
def handle_in ( "shout" , payload , socket ) do
Chat.Message . changeset ( % Chat.Message { } , payload ) |> Chat.Repo . insert
broadcast socket , "shout" , payload
{ :noreply , socket }
end Si se dio cuenta anteriormente, en nuestro archivo de assets/js/app.js , utilizamos la función sendMessage() para enviar nuestro mensaje al servidor en el evento "gritar".
Phoenix enruta el mensaje a la función handle_in("shout", payload, socket) del lado del servidor porque el nombre del evento coincide con el 'grito'.
En esta función, manejamos la carga útil (que es el texto del mensaje y cualquier otro dato) e insertamos en nuestra base de datos. ¡Limpio!
Abra el archivo lib/chat/message.ex e importe Ecto.Query :
defmodule Chat.Message do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query # add Ecto.Query
Luego agregue una nueva función:
def get_messages ( limit \ 20 ) do
Chat.Message
|> limit ( ^ limit )
|> order_by ( desc: :inserted_at )
|> Chat.Repo . all ( )
end Esta función acepta un solo limit de parámetros para devolver solo un número fijo/máximo de registros. Utiliza la función all de ecto para obtener todos los registros de la base de datos. Message es el nombre del esquema/tabla para la que queremos obtener registros, y el límite es el número máximo de registros para obtener.
En el archivo /lib/chat_web/channels/room_channel.ex crea una nueva función:
@ impl true
def handle_info ( :after_join , socket ) do
Chat.Message . get_messages ( )
|> Enum . reverse ( ) # revers to display the latest message at the bottom of the page
|> Enum . each ( fn msg -> push ( socket , "shout" , % {
name: msg . name ,
message: msg . message ,
inserted_at: msg . inserted_at ,
} ) end )
{ :noreply , socket } # :noreply
end y en la parte superior de la actualización del archivo la función join a lo siguiente:
def join ( "room:lobby" , payload , socket ) do
if authorized? ( payload ) do
send ( self ( ) , :after_join )
{ :ok , socket }
else
{ :error , % { reason: "unauthorized" } }
end
endNota : Al igual que la Sección 7, Phoenix sabe llamar a esta función cuando el servidor envía el mensaje interno
:after_joina través del proceso del canal.Nuestra función
join/3enlib/chat_web/channels/room_channel.exenvía que:after_join messageal proceso del canal cuando el cliente se conecta con éxito al tema"room:lobby".
Inicie el servidor Phoenix ( si aún no se está ejecutando ):
mix phx.serverNota : Tomará unos segundos para compilar .
En su terminal, debería ver:
[info] Running ChatWeb.Endpoint with cowboy 2.8.0 at 0.0.0.0:4000 (http)
[info] Access ChatWeb.Endpoint at http://localhost:4000
webpack is watching the files… ¡Esto nos dice que nuestro código compilado ( como se esperaba ) y la aplicación de chat se ejecuta en el puerto TCP 4000 !
Abra la aplicación web de chat en dos ventanas de navegador separadas : http: // localhost: 4000
( Si su máquina solo tiene un navegador, intente usar una pestaña "Incognito" )
Debería poder enviar mensajes entre las dos ventanas del navegador: 
¡Felicidades! ¡Tiene una aplicación de chat ( básica ) que funciona en Phoenix!
¡El historial de chat (mensaje) se guarda !
Esto significa que puede actualizar el navegador o unirse en un navegador diferente y aún verá la historia.
Las pruebas automatizadas son una de las mejores maneras de garantizar la confiabilidad en sus aplicaciones web.
Nota : Si es completamente nuevo en las pruebas automatizadas o el "desarrollo impulsado por las pruebas" ("TDD"), recomendamos leer/seguir el tutorial "básico": github.com/dwyl/ Learn-tdd
Las pruebas en Phoenix son rápidas ( ¡las pruebas se ejecutan en paralelo! ) Y son fáciles de comenzar! El marco de pruebas ExUnit está incorporada, por lo que no hay "decisiones/debates" sobre qué marco o estilo usar.
Si nunca ha visto o escrito una prueba con ExUnit , no teme, la sintaxis debe ser familiar si ha escrito algún tipo de prueba automatizada en el pasado.
Siempre que cree una nueva aplicación Phoenix o agregue una nueva función ( como un canal ), Phoenix genera una nueva prueba para usted.
Ejecutamos las pruebas usando el comando mix test :
... ... ..
Finished in 0.1 seconds ( 0.05 s async , 0.06 s sync )
8 tests , 0 failures
Randomized with seed 157426En este caso, ninguna de estas pruebas falla. ( 8 pruebas, 0 falla )
Vale la pena tomarse un momento ( ¡o todo el tiempo que necesite !) Para comprender lo que está sucediendo en el archivo /room_channel_test.exs . Ábrelo si aún no lo ha hecho, lea las descripciones de prueba y el código.
Para un poco de contexto , recomendamos leer: https://hexdocs.pm/phoenix/ testing_channels .html
Echemos un vistazo a la primera prueba en /test/chat_web/channels/room_channel_test.exs:
test "ping replies with status ok" , % { socket: socket } do
ref = push socket , "ping" , % { "hello" => "there" }
assert_reply ref , :ok , % { "hello" => "there" }
end La prueba obtiene el socket de la función setup ( en la línea 6 del archivo ) y asigna el resultado de llamar a la función push a una ref variable, push simplemente presiona un mensaje ( el mapa %{"hello" => "there"} ) en el socket al tema "ping" .
La cláusula de función handle_in que maneja el tema "ping" :
def handle_in ( "ping" , payload , socket ) do
{ :reply , { :ok , payload } , socket }
end Simplemente responde con la carga útil que la envía, por lo tanto, en nuestra prueba podemos usar la macro assert_reply para afirmar que el ref es igual a :ok, %{"hello" => "there"}
Nota : Si tiene preguntas o necesita ayuda para comprender las otras pruebas, ¡abra un problema en GitHub, nos complace expandir esto aún más!
( Solo estamos tratando de mantener este tutorial razonablemente "breve", por lo que los principiantes no están "abrumados" por nada ...)
A menudo podemos aprender mucho sobre una aplicación ( o API ) para leer las pruebas y ver dónde están las "brechas" en las pruebas.
Afortunadamente podemos lograr esto con solo un par de pasos:
excoveralls como dependencia (de desarrollo) para mix.exs Abra su archivo mix.exs y busque la función "DEPS":
defp deps do
Agregue una coma al final de la última línea, luego agregue la siguiente línea al final de la lista:
{ :excoveralls , "~> 0.15.2" , only: [ :test , :dev ] } # tracking test coverage Además, encuentre la sección def project do ( hacia la parte superior de mix.exs ) y agregue las siguientes líneas a la lista:
test_coverage : [ tool : E xCoveralls ] ,
preferred_cli_env: [
coveralls : :test ,
"coveralls.detail": :test ,
"coveralls.post": :test ,
"coveralls.html": :test
] Luego , instale la dependencia de excoveralls que acabamos de agregar a mix.exs :
mix deps.getDeberías ver:
Resolving Hex dependencies...
Dependency resolution completed:
* Getting excoveralls (Hex package)
... etc.coveralls.json En el "Root" ( directorio base ) del proyecto de chat, cree un nuevo archivo llamado coveralls.json y Copy-Paste lo siguiente:
{
"coverage_options" : {
"minimum_coverage" : 100
},
"skip_files" : [
" test/ " ,
" lib/chat/application.ex " ,
" lib/chat_web.ex " ,
" lib/chat_web/telemetry.ex " ,
" lib/chat_web/components/core_components.ex " ,
" lib/chat_web/channels/user_socket.ex "
]
}
Este archivo es bastante básico, instruye a la aplicación coveralls que requiera un minimum_coverage del 100% ( es decir, todo se prueba 1 ) e ignora los archivos en la test/ directorio para verificar la cobertura. También ignoramos archivos como application.ex , telemetry.ex , core_components.ex y user_socket.ex porque no son relevantes para la funcionalidad de nuestro proyecto.
1 Creemos que invertir un poco de tiempo por adelantado para escribir pruebas para todo nuestro código vale la pena tener menos errores más adelante.
Los errores son caros , las pruebas son baratas y la confianza / confiabilidad no tiene precio .
Para ejecutar las pruebas con cobertura, copiar el siguiente comando en su terminal:
MIX_ENV = test mix do coveralls . json
Para el uso de Windows:
$ env :MIX_ENV = "test" ; mix do coveralls . json
Deberías ver:
Randomized with seed 527109
----------------
COV FILE LINES RELEVANT MISSED
100.0% lib/chat.ex 9 0 0
100.0% lib/chat/message.ex 26 4 0
100.0% lib/chat/repo.ex 5 0 0
70.0% lib/chat_web/channels/room_channel.ex 46 10 3
100.0% lib/chat_web/components/layouts.ex 5 0 0
100.0% lib/chat_web/controllers/error_html.ex 19 1 0
100.0% lib/chat_web/controllers/error_json.ex 15 1 0
100.0% lib/chat_web/controllers/page_controller 9 1 0
100.0% lib/chat_web/controllers/page_html.ex 5 0 0
100.0% lib/chat_web/endpoint.ex 49 0 0
66.7% lib/chat_web/router.ex 27 3 1
[TOTAL] 80.0%
----------------
Como podemos ver aquí, solo el 80% de las líneas de código en /lib están siendo "cubiertas" por las pruebas que hemos escrito.
Para ver la cobertura en un navegador web, ejecute lo siguiente:
MIX_ENV = test mix coveralls . html ; open cover / excoveralls . html Esto abrirá el informe de cobertura (HTML) en su navegador web predeterminado:

Abra el archivo test/chat_web/channels/room_channel_test.exs y agregue la siguiente prueba:
test ":after_join sends all existing messages" , % { socket: socket } do
# insert a new message to send in the :after_join
payload = % { name: "Alex" , message: "test" }
Chat.Message . changeset ( % Chat.Message { } , payload ) |> Chat.Repo . insert ( )
{ :ok , _ , socket2 } = ChatWeb.UserSocket
|> socket ( "person_id" , % { some: :assign } )
|> subscribe_and_join ( ChatWeb.RoomChannel , "room:lobby" )
assert socket2 . join_ref != socket . join_ref
end Finalmente, dentro de lib/chat_web/router.ex , comente la siguiente pieza de código.
pipeline :api do
plug :accepts , [ "json" ]
end Como no estamos usando esto :api en este proyecto, no hay necesidad de probarlo.
Ahora, cuando ejecuta MIX_ENV=test mix do coveralls.json debería ver:
Randomized with seed 15920
----------------
COV FILE LINES RELEVANT MISSED
100.0% lib/chat.ex 9 0 0
100.0% lib/chat/message.ex 26 4 0
100.0% lib/chat/repo.ex 5 0 0
100.0% lib/chat_web/channels/room_channel.ex 46 10 0
100.0% lib/chat_web/components/layouts.ex 5 0 0
100.0% lib/chat_web/controllers/error_html.ex 19 1 0
100.0% lib/chat_web/controllers/error_json.ex 15 1 0
100.0% lib/chat_web/controllers/page_controller 9 1 0
100.0% lib/chat_web/controllers/page_html.ex 5 0 0
100.0% lib/chat_web/endpoint.ex 49 0 0
100.0% lib/chat_web/router.ex 27 2 0
[TOTAL] 100.0%
----------------
Esta prueba solo crea un mensaje antes de la subscribe_and_join , por lo que hay un mensaje en la base de datos para enviar a cualquier clien que se una al chat.
De esa manera, el :after_join tiene al menos un mensaje y el Enum.each se invocará al menos una vez.
¡Con eso nuestra aplicación está completamente probada!
Podemos extender este proyecto para admitir la autenticación básica. Si desea comprender cómo se implementa la autenticación de la manera fácil/rápida , consulte: Auth.MD
Presence a rastrear quién está en línea Una de las grandes ventajas del uso Phoenix es que puede rastrear fácilmente los procesos y canales.
¡Esto allana el camino para mostrar sin esfuerzo quién está en línea o no!
Si está interesado en desarrollar esta función, hemos creado una guía en presence.md ¡MD solo para usted! ?
La integración continua le permite automatizar la ejecución de las pruebas para verificar/confirmar que su aplicación funciona como se esperaba ( antes de implementar ). Esto evita accidentalmente " romper " su aplicación.
Afortunadamente, los pasos son bastante simples.
Para un ejemplo ci.yml , ver:
.github/workflows/ci.yml
El despliegue para Fly.io toma un par de minutos, recomendamos seguir la guía oficial: Fly.io/docs/elixir/
Una vez que haya implementado , podrá ver/usar su aplicación en cualquier navegador web/móvil.
por ejemplo: Phoenix-chat .fly.dev/

Si encontró este ejemplo útil, por favor, por favor, el repositorio de GitHub para que nosotros ( y otros ) sepamos que le gustó.
Si desea obtener más información de Phoenix y la magia de LiveView , considere leer nuestro tutorial para principiantes: github.com/dwyl/ phoenix-liveview-counter-tutorial
Para una versión de una aplicación de chat usando LiveView , puede leer el siguiente repositorio: github.com/dwyl/ phoenix-liveview-chat-example
¡Gracias por aprender con nosotros! ☀️
Este repositorio está inspirado en el simple ejemplo de chat de @Chrismccord: https://github.com/chrismccord/phoenix_chat_example ❤️
Al momento de escribir, el ejemplo de Chris se actualizó por última vez el 20 de febrero de 2018 y usa Phoenix 1.3 Ver: números/40.
Hay bastantes diferencias (cambios de ruptura) entre Phoenix 1.3 y 1.6 ( la última versión ).
Nuestro tutorial utiliza Phoenix 1.6.2 (más reciente a octubre de 2021). Nuestra esperanza es que al escribir ( y mantener ) un tutorial centrado paso a paso enfocado para principiantes contribuyamos a la comunidad Elixir/Phoenix sin acumular PRS en el repositorio de Chris.