
Experimente : phoenix-chat .fly.dev
Um tutorial passo a passo para construção, teste e implantação de um aplicativo de bate-papo em Phoenix!
page_controller_test.exsuser_socket.jsexcoveralls como uma dependência (de desenvolvimento) ao mix.exscoveralls.jsonPresence para rastrear quem está online Os aplicativos de bate -papo são os "Hello World" dos exemplos em tempo real.
Infelizmente, a maioria dos aplicativos de exemplo mostra alguns princípios básicos e depois ignora o resto ...? ♀️
Portanto, os iniciantes geralmente ficam perdidos ou confusos quanto ao que devem fazer ou aprender a seguir !
Muito poucos tutoriais consideram testes, implantação, documentação ou outros " aprimoramentos ", que fazem parte do " mundo real " da construção e execução de aplicativos; Portanto, esses são tópicos que abordaremos para " preencher as lacunas ".
Escrevemos este tutorial para ser a maneira mais fácil de aprender Phoenix , Ecto e Channels com um exemplo prático que alguém pode seguir .
Este é o exemplo/tutorial que desejávamos quando estávamos aprendendo Elixir , Phoenix ... se você achar útil, por favor, obrigado!
Um simples tutorial passo a passo mostrando como:
mix phx.new chat "generator" )Fly.io para que você possa mostrar às pessoas sua criação!Inicialmente , pulavamos deliberadamente arquivos de configuração e " Phoenix Internals " porque você ( iniciantes ) não precisa saber sobre eles para começar . Mas não se preocupe, voltaremos a eles quando necessário . Apreciamos o aprendizado de " just-in-time " ( quando você precisar ), pois é imediatamente óbvio e prático por que estamos aprendendo algo.
Este exemplo é para iniciantes completos como um aplicativo " meu primeiro Phoenix ".
Tentamos assumir o mínimo possível, mas se você acha que " pulamos um passo " ou se sentirá " preso " por qualquer motivo ou tiver alguma dúvida ( relacionada a este exemplo ), abra um problema no Github!
As comunidades @Dwyl e Phoenix são super amigas para iniciantes , então não tenha medo/tímido.
Além disso, fazendo perguntas, você está ajudando todos que estão ou podem ficar presos à mesma coisa!
Essas instruções mostram como criar o aplicativo de bate -papo do zero .
brew install elixirNota : Se você já possui
Elixirinstalado no seu Mac, e só deseja atualizar para a versão mais recente, Run:brew upgrade elixir
mix archive.install hex phx_new O conhecimento básico da sintaxe da elixir ajudará,
Por favor, veja: Dwyl/ Learn-Elixir
O conhecimento básico do JavaScript é vantajoso ( mas não essencial, pois o código "front-end" é bastante básico e bem contratado ). Veja: Dwyl/JavaScript-The-Good-Parts-Nota
Verifique se você tem a versão mais recente do Elixir ( execute o seguinte comando em seu terminal ):
elixir -vVocê deve 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 se você tem a versão mais recente do Phoenix :
mix phx.new -vVocê deveria ver:
Phoenix installer v1.7.0-rc.2NOTA : Se a sua versão
Phoenixfor mais recente , sinta -se à vontade para atualizar este documento! Tentamos o nosso melhor para mantê -lo atualizado ... mas suas contribuições são sempre bem -vindas!
Neste tutorial, estamos usando o Phoenix 1.7-RC2, o segundo candidato a
Phoenix 1.7. No momento da redação deste artigo, se você instalar o Phoenix, a versão estável mais recente não seráv1.7. Para usar esta versão, siga o guia oficial (não se preocupe, está apenas executando um comando!)-> https://www.phoenixframework.org/blog/phoenix-1.7-RellededNo entanto, se você estiver lendo isso após o lançamento,
v1.7será instalado para você e você veráPhoenix installer v1.7.0no seu terminal.
Confirmar PostgreSQL está em execução ( para que o aplicativo possa armazenar mensagens de bate -papo ) Execute o seguinte comando:
lsof -i :5432Você deve ver a saída semelhante ao seguinte:
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) Isso nos diz que o PostgreSQL está " ouvindo " na porta TCP 5432 ( a porta padrão )
Se o comando lsof não produzir nenhum resultado no seu terminal, execute:
pg_isreadyDeve imprimir o seguinte:
/tmp:5432 - accepting connectionsCom todas as "verificações pré-voo" realizadas, vamos voar !
Antes de tentar criar o aplicativo de bate -papo do zero, clonar e executar a versão de trabalho acabada para ter uma idéia do que esperar.
No seu terminal, execute o seguinte comando para clonar o repositório:
git clone [email protected]:dwyl/phoenix-chat-example.git Mude no diretório phoenix-chat-example e instale as dependências Elixir e Node.js com este comando:
cd phoenix-chat-example
mix setupExecute o aplicativo Phoenix com o comando:
mix phx.serverSe você abrir o App localhost: 4000 em mais dois navegadores da web, poderá ver as mensagens de bate -papo exibidas em todas elas assim que acertar a tecla Enter :

Agora que você confirmou que o aplicativo de bate -papo de Phoenix acabado funciona em sua máquina, é hora de construí -lo do zero!
Diretório de mudança:
cd ..E comece a construir!
No seu programa de terminal em sua localhost, digite o seguinte comando para criar o aplicativo:
mix phx.new chat --no-mailer --no-dashboard --no-gettext Isso criará a estrutura do diretório e os arquivos do projeto.
Estamos executando o comando
mix phx.newcom os argumentos--no-mailer--no-dashboard--no-gettext, porque não queremos que nosso projeto gere arquivos Mailer, para incluir umPhoenix.LiveDashboarde gerar arquivosgettext(parai18n).
Quando solicitado a " buscar e instalar dependências ? [Yn]",
Tipo Y no seu terminal, seguido pela tecla Enter ( Return ).
Você deveria ver: 
Altere o diretório no diretório chat executando o comando sugerido:
cd chatAgora execute o seguinte comando:
mix setupNOTA : Neste ponto, já existe um "aplicativo" que ainda não faz nada (ainda) ...
Você pode executarmix phx.serverem seu terminal - não se preocupe se você estiver vendo erro
Mensagens, isso ocorre porque ainda não criamos nosso banco de dados.
Vamos cuidar disso na etapa 6!
Por enquanto, aberto http: // localhost: 4000 em seu navegador
E você verá a páginadefault"Bem -vindo à Phoenix":

Desligue o servidor Phoenix no seu terminal com o comando Ctrl + C.
Na janela do seu terminal, execute o seguinte comando:
mix test
Você deve ver a saída semelhante ao seguinte:
Generated chat app
.....
Finished in 0.02 seconds (0.02s async, 0.00s sync)
5 tests, 0 failures
Randomized with seed 84184Agora que confirmamos que tudo está funcionando (todos os testes passam), vamos continuar com a parte interessante !
Gere o canal (webSocket) a ser usado no aplicativo de bate -papo:
mix phx.gen.channel RoomSe você for solicitado a confirmar a instalação de um novo manipulador de soquete Tipo
ye pressione a tecla[Enter].
Isso criará três arquivos :
* creating lib/chat_web/channels/room_channel.ex
* creating test/chat_web/channels/room_channel_test.exs
* creating test/support/channel_case.exAlém de criar mais dois arquivos :
* creating lib/chat_web/channels/user_socket.ex
* creating assets/js/user_socket.js O arquivo room_channel.ex lida com mensagens de recebimento/envio e o room_channel_test.exs testa a interação básica com o canal. Vamos nos concentrar nos arquivos socket criados posteriormente. ( Não se preocupe com isso ainda, veremos o arquivo de teste na etapa 14 abaixo !)
Somos informados de que precisamos atualizar um pedaço de código em nosso aplicativo:
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 " O gerador nos pede para importar o código do cliente no front -end. Vamos fazer isso mais tarde. Por enquanto, abra o arquivo lib/chat_web/endpoint.ex e siga as instruções.
Depois disso, abra o arquivo chamado /lib/chat_web/channels/user_socket.ex
e mude a linha:
channel "room:*" , ChatWeb.RoomChannelpara:
channel "room:lobby" , ChatWeb.RoomChannelVerifique a alteração aqui.
Isso garantirá que quaisquer mensagens enviadas para "room:lobby" sejam roteadas para o nosso RoomChannel .
A "room.* anterior significava que qualquer subtópico dentro "room" foi encaminhado. Mas, por enquanto, vamos diminuir para apenas um subtópico?.
Para mais detalhes sobre os canais da Phoenix, ( é altamente recomendável ), leia: https://hexdocs.pm/phoenix/channels.html
Abra o /lib/chat_web/controllers/page_html/home.html.heex arquivo
e copiar colar ( ou tipo ) o seguinte 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 é o formulário básico que usaremos para inserir mensagens de bate -papo.
As classes, por exemplo w-full e items-center são aulas TailwindCSS para estilizar o formulário.
Phoenix inclui Tailwind por padrão para que você possa se aprofundar com seu aplicativo/ideia/"MVP"!
Se você é novo no
Tailwind, consulte: Dwyl/ Learn-TailwindSe você tiver dúvidas sobre qualquer uma das aulas
Tailwindusadas, gaste 2 minutos no Google ou pesquisando os documentos oficiais (excelentes!): Tailwindcss.com/docs e, se você ainda estiver preso, abra um problema.
Seu arquivo de modelo home.html.heex deve ser assim: /lib/chat_web/controllers/page_html/home.html.heex
Abra o arquivo lib/chat_web/components/layouts/root.html.heex e localize a tag <body> . Substitua o conteúdo do <body> pelo seguinte 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 > Seu arquivo de modelo root.html.heex deve ser assim: /lib/chat_web/components/layouts/root.html.heex
No final desta etapa, se você executar o Phoenix Server mix phx.server , e veja o aplicativo no seu navegador, ele ficará assim:

Portanto, já está começando a parecer um aplicativo de bate -papo básico. Infelizmente, desde que mudamos a cópia do home.html.heex , nossa page_controller_test.exs agora falha:
Execute o 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"
Felizmente, isso é fácil de corrigir.
page_controller_test.exs Abra o arquivo test/chat_web/controllers/page_controller_test.exs e substitua a linha:
assert html_response ( conn , 200 ) =~ "Peace of mind from prototype to production"Com:
assert html_response ( conn , 200 ) =~ "Phoenix Chat Example"Agora, se você executar os testes novamente, eles passarão:
mix test
Saída de amostra:
........
Finished in 0.1 seconds (0.09s async, 0.06s sync)
8 tests, 0 failures
Randomized with seed 275786
ABERTA assets/js/app.js , Uncomment e Altere a linha:
import socket from "./user_socket.js" Com a linha não tomada , nosso aplicativo importará o arquivo socket.js , que nos dará funcionalidade do WebSocket.
Em seguida, adicione o seguinte código JavaScript ("Client") à parte inferior do arquivo:
/* 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 ) ;
}Reserve um momento para ler o código JavaScript e confirmar sua compreensão do que está fazendo.
Esperançosamente, os comentários em linha são auto-explicativos, mas se algo não estiver claro, pergunte!
Neste ponto, seu arquivo app.js deve ser assim: /assets/js/app.js
user_socket.js Por padrão, o Phoenix Channel (Client) assinará a sala genérica: "topic:subtopic" . Como não vamos usar isso, podemos evitar ver qualquer erro "unable to join: unmatched topic" em nosso navegador/console, simplesmente comentando algumas linhas no arquivo user_socket.js . Abra o arquivo em seu editor e localize as seguintes linhas:
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 ) } )Comente as linhas para que elas não sejam executadas:
//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) }) Seu user_socket.js agora deve se parecer com o seguinte: /assets/js/user_socket.js
Se você decidir mais tarde arrumar seu aplicativo de bate -papo, poderá
deleteessas linhas comentadas do arquivo.
Estamos apenas mantendo -os para referência de como ingressar nos canais e receber mensagens.
Se você estiver executando o aplicativo, tente preencher o name e os campos message e clique em Enter (ou pressione Send ).
A mensagem deve aparecer em diferentes janelas!

Com isso feito, podemos prosseguir.
Se não quiséssemos salvar o histórico de bate -papo, poderíamos implantar esse aplicativo imediatamente e teríamos feito!
De fato, pode ser um " recurso de uso "/"" Recurso "ter um bate-papo" efêmeral "sem qualquer história ... Veja: http://www.psstchat.com/.
Mas estamos assumindo que a maioria dos aplicativos de bate -papo salva histórico para que
newpessoas que ingressam no "canal" possam ver a história e as pessoas que estão brevemente "ausentes" podem "recuperar o atraso" da história.
Execute o seguinte comando em seu terminal:
mix phx.gen.schema Message messages name:string message:stringVocê deve ver a seguinte saída:
* creating lib/chat/message.ex
* creating priv/repo/migrations/20230203114114_create_messages.exs
Remember to update your repository by running migrations:
$ mix ecto.migrateVamos quebrar esse comando para clareza:
mix phx.gen.schema - o comando mix para criar um novo esquema (tabela de banco de dados)Message - O nome singular para gravar em nossas mensagens "coleção"messages - o nome da coleção ( ou tabela de banco de dados )name:string - o nome da pessoa que envia uma mensagem, armazenada como uma string .message:string - A mensagem enviada pela pessoa, também armazenada como uma string . A linha creating lib/chat/message.ex cria o "esquema" para a nossa tabela de banco de dados de mensagens.
Além disso, um arquivo de migração é criado, por exemplo: creating priv/repo/migrations/20230203114114_create_messages.exs A " migração " realmente cria a tabela de banco de dados em nosso banco de dados.
No seu terminal, execute o seguinte comando para criar a tabela messages :
mix ecto.migratePara contexto, recomendamos a leitura: hexdocs.pm/ecto_sql/ ecto.migration .html
Você deve ver o seguinte em seu 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 Se você abrir seu PostGresql GUI ( por exemplo: pgadmin ), verá que a tabela de mensagens foi criada no banco de dados chat_dev :

Você pode visualizar o esquema da tabela " clicando com o botão direito do mouse " ( ctrl + click em Mac ) na tabela messages e selecionando "Propriedades":

NOTA : Para as seções 7, 8 e 9, estaremos desenvolvendo como nosso código "lida" com os diferentes eventos que podem ocorrer em nosso aplicativo de bate -papo.
Phoenix abstrava grande parte da lógica de passagem de mensagens subjacente na comunicação do processo do Elixir (para obter mais informações sobre como os processos do ELIXIR se comunicam, leia aqui).
Em Phoenix, os eventos/mensagens enviados do cliente são automaticamente roteados para as funções manipuladoras correspondentes com base no nome do evento, tornando a manuseio de mensagens perfeita e direta!.
Abra o arquivo lib/chat_web/channels/room_channel.ex e dentro da função def handle_in("shout", payload, socket) do Adicione a seguinte linha:
Chat.Message . changeset ( % Chat.Message { } , payload ) |> Chat.Repo . insert Para que sua função acaba assim:
def handle_in ( "shout" , payload , socket ) do
Chat.Message . changeset ( % Chat.Message { } , payload ) |> Chat.Repo . insert
broadcast socket , "shout" , payload
{ :noreply , socket }
end Se você notou anteriormente, em nossos assets/js/app.js , usamos a função sendMessage() para levar nossa mensagem ao servidor no evento "shout".
Phoenix direciona a mensagem para a função handle_in("shout", payload, socket) porque o nome do evento corresponde a 'shout'.
Nesta função, lidamos com a carga útil (que é o texto da mensagem e quaisquer outros dados) e o inserimos em nosso banco de dados. Organizado!
Abra o arquivo lib/chat/message.ex e importe Ecto.Query :
defmodule Chat.Message do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query # add Ecto.Query
Em seguida, adicione uma nova função:
def get_messages ( limit \ 20 ) do
Chat.Message
|> limit ( ^ limit )
|> order_by ( desc: :inserted_at )
|> Chat.Repo . all ( )
end Esta função aceita um único limit parâmetro para retornar apenas um número fixo/máximo de registros. Ele usa all a função da Ecto para buscar todos os registros do banco de dados. Message é o nome do esquema/tabela para as quais queremos obter registros e o limite é o número máximo de registros a serem buscados.
No arquivo /lib/chat_web/channels/room_channel.ex crie uma nova função:
@ 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 E na parte superior do arquivo, atualize a função join para o seguinte:
def join ( "room:lobby" , payload , socket ) do
if authorized? ( payload ) do
send ( self ( ) , :after_join )
{ :ok , socket }
else
{ :error , % { reason: "unauthorized" } }
end
endNOTA : Como a Seção 7, Phoenix sabe chamar essa função quando o servidor enviar a mensagem interna
:after_joinatravés do processo de canal.Nossa função
join/3nalib/chat_web/channels/room_channel.exenvia isso:after_join messagepara o processo de canal quando o cliente se conecta com êxito ao tópico"room:lobby".
Inicie o servidor Phoenix ( se ainda não estiver em execução ):
mix phx.serverNota : levará alguns segundos para compilar .
No seu terminal, você deve 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… Isso nos diz que nosso código compilou ( como esperado ) e o aplicativo de bate -papo está em execução na porta TCP 4000 !
Abra o aplicativo da web de bate -papo em duas janelas de navegador separadas : http: // localhost: 4000
( Se sua máquina tiver apenas um navegador, tente usar uma guia "Incognito" )
Você poderá enviar mensagens entre as duas janelas do navegador: 
Parabéns! Você tem um aplicativo de bate -papo ( BASIC ) escrito em Phoenix!
O histórico de bate -papo (mensagem) é salvo !
Isso significa que você pode atualizar o navegador ou se juntar a um navegador diferente e ainda verá a história!
O teste automatizado é uma das melhores maneiras de garantir confiabilidade em seus aplicativos da Web.
NOTA : Se você é completamente novo em testes automatizados ou "desenvolvimento orientado a testes" ("TDD"), recomendamos a leitura/acompanhamento do tutorial "Basic": github.com/dwyl/ lettle-tdd
Os testes em Phoenix são rápidos ( os testes são executados em paralelo! ) E fáceis de começar! A estrutura de teste ExUnit está embutida, portanto não há "decisões/debates" sobre qual estrutura ou estilo usar.
Se você nunca viu ou escreveu um teste com ExUnit , não tema, a sintaxe deve ser familiar se você tiver escrito algum tipo de teste automatizado no passado.
Sempre que você cria um novo aplicativo Phoenix ou adiciona um novo recurso ( como um canal ), o Phoenix gera um novo teste para você.
Executamos os testes usando o comando mix test :
... ... ..
Finished in 0.1 seconds ( 0.05 s async , 0.06 s sync )
8 tests , 0 failures
Randomized with seed 157426Nesse caso, nenhum desses testes falha. ( 8 testes, 0 falha )
Vale a pena levar um momento ( ou o tempo que você precisar !) Para entender o que está acontecendo no arquivo /room_channel_test.exs . Abra -o se ainda não o fizer, leia as descrições e o código do teste.
Para um pouco de contexto , recomendamos a leitura: https://hexdocs.pm/phoenix/ testing_channels .html
Vamos dar uma olhada no primeiro teste em /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 O teste recebe o socket da função setup ( na linha 6 do arquivo ) e atribui o resultado de chamar a função push a uma variável ref push apenas empurra uma mensagem ( o mapa %{"hello" => "there"} ) no tópico socket "ping" .
A cláusula de função handle_in que lida com o tópico "ping" :
def handle_in ( "ping" , payload , socket ) do
{ :reply , { :ok , payload } , socket }
end Simplesmente responde com a carga útil que você envia, portanto, em nosso teste, podemos usar a macro assert_reply para afirmar que o ref é igual a :ok, %{"hello" => "there"}
NOTA : Se você tiver dúvidas ou precisar de ajuda para entender os outros testes, abra um problema no Github, estamos felizes em expandir isso mais!
( Estamos apenas tentando manter este tutorial razoavelmente "breve" para que os iniciantes não sejam "sobrecarregados" por qualquer coisa ...)
Muitas vezes, podemos aprender muito sobre um aplicativo ( ou API ) lendo os testes e vendo onde estão as "lacunas" nos testes.
Felizmente, podemos conseguir isso com apenas algumas etapas:
excoveralls como uma dependência (de desenvolvimento) ao mix.exs Abra seu arquivo mix.exs e encontre a função "Deps":
defp deps do
Adicione uma vírgula ao final da última linha e adicione a seguinte linha ao final da lista:
{ :excoveralls , "~> 0.15.2" , only: [ :test , :dev ] } # tracking test coverage Além disso, encontre a seção def project do ( na parte superior do mix.exs ) e adicione as seguintes linhas à lista:
test_coverage : [ tool : E xCoveralls ] ,
preferred_cli_env: [
coveralls : :test ,
"coveralls.detail": :test ,
"coveralls.post": :test ,
"coveralls.html": :test
] Em seguida , instale a dependência de excoveralls que acabamos de adicionar ao mix.exs :
mix deps.getVocê deveria ver:
Resolving Hex dependencies...
Dependency resolution completed:
* Getting excoveralls (Hex package)
... etc.coveralls.json No "ROOT" ( diretório base ) do projeto de bate-papo, crie um novo arquivo chamado coveralls.json e copie o seguinte:
{
"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 "
]
}
Esse arquivo é bastante básico, instrui o aplicativo coveralls a exigir uma minimum_coverage de 100% ( ou seja, tudo é testado 1 ) e ignorar os arquivos no test/ diretório para verificação de cobertura. Também ignoramos arquivos como application.ex , telemetry.ex , core_components.ex e user_socket.ex porque eles não são relevantes para a funcionalidade do nosso projeto.
1 Acreditamos que investir um pouco de tempo adiante para escrever testes para todo o nosso código vale a pena ter menos bugs depois.
Os bugs são caros , os testes são baratos e a confiança / confiabilidade não tem preço .
Para executar os testes com cobertura, copie o comando a seguir em seu terminal:
MIX_ENV = test mix do coveralls . json
Para uso do Windows:
$ env :MIX_ENV = "test" ; mix do coveralls . json
Você deveria 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 aqui, apenas 80% das linhas de código em /lib estão "cobertas" pelos testes que escrevemos.
Para ver a cobertura em um navegador da web, execute o seguinte:
MIX_ENV = test mix coveralls . html ; open cover / excoveralls . html Isso abrirá o relatório de cobertura (HTML) no seu navegador padrão:

Abra o arquivo test/chat_web/channels/room_channel_test.exs e adicione o seguinte teste:
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 lib/chat_web/router.ex , comente a seguinte parte do código.
pipeline :api do
plug :accepts , [ "json" ]
end Como não estamos usando isso :api neste projeto, não há necessidade de testá -lo.
Agora, quando você executa MIX_ENV=test mix do coveralls.json você deve 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%
----------------
Este teste apenas cria uma mensagem antes do subscribe_and_join , para que haja uma mensagem no banco de dados para enviar para qualquer clien que se junte ao bate -papo.
Dessa forma, o :after_join tem pelo menos uma mensagem e o Enum.each será chamado pelo menos uma vez.
Com isso, nosso aplicativo está totalmente testado!
Podemos estender este projeto para apoiar a autenticação básica. Se você quiser entender como a autenticação é implementada da maneira fácil/rápida , consulte: auth.md
Presence para rastrear quem está online Uma das grandes vantagens do uso Phoenix é que você pode rastrear facilmente processos e canais.
Isso abre o caminho para mostrar sem esforço quem está online ou não!
Se você estiver interessado em desenvolver esse recurso, criamos um guia em presence.md apenas para você! ?
A integração contínua permite automatizar a execução dos testes para verificar/confirmar que seu aplicativo está funcionando como esperado ( antes de implantar ). Isso impede acidentalmente " quebrar " seu aplicativo.
Felizmente, as etapas são bastante simples.
Por exemplo, ci.yml , consulte:
.github/workflows/ci.yml
A implantação para voar.io leva alguns minutos, recomendamos seguir o guia oficial: Fly.io/docs/elixir/ finge-started
Depois de implantar , você poderá visualizar/usar seu aplicativo em qualquer navegador da Web/Mobile.
por exemplo: Phoenix-chat .fly.dev/

Se você achou este exemplo útil, por favor ️ o repositório do GitHub para que nós ( e outros ) saibamos que você gostamos!
Se você quiser aprender mais Phoenix e a magia do LiveView , considere ler o tutorial do nosso iniciante: github.com/dwyl/ phoenix-liveview-counter-tutorials
Para uma versão de um aplicativo de bate-papo usando o LiveView , você pode ler o seguinte repositório: github.com/dwyl/ phoenix-liveview-clat-example
Obrigado por aprender conosco! ☀️
Este repo é inspirado no exemplo de bate -papo simples do @chrismccord: https://github.com/chrismccord/phoenix_chat_example ❤️
No momento da redação, o exemplo de Chris foi atualizado pela última vez em 20 de fevereiro de 2018 e usa o Phoenix 1.3 Ver: Edições/40.
Existem algumas diferenças (mudanças de quebra) entre Phoenix 1.3 e 1.6 ( a versão mais recente ).
Nosso tutorial usa o Phoenix 1.6.2 (mais recente em outubro de 2021). Nossa esperança é que, ao escrever ( e manter ) um tutorial focado em iniciantes passo a passo, contribuamos para a comunidade Elixir/Phoenix sem acumular PRS no repositório de Chris.