⬆️ ⬇️

Creating a blog engine with Phoenix and Elixir / Part 5. Connecting ExMachina





From the translator: “ Elixir and Phoenix are a great example of where modern web development is heading. Already, these tools provide high-quality access to real-time technologies for web applications. Sites with increased interactivity, multiplayer browser games, microservices - those areas in which these technologies will serve a good service. The following is a translation of a series of 11 articles that describe in detail the aspects of development on the Phoenix framework that would seem such a trivial thing as a blog engine. But do not hurry to sulk, it will be really interesting, especially if the articles encourage you to pay attention to the Elixir or become its followers.



In this part, we will include the ExMachina library to improve the testing process. Now you don’t need to copy identical code to create test models, factories will do it for us!



At the moment, our application is based on:

')



Introduction



As you noticed, in the process of writing this engine, we use only a few libraries. Now add another one called ExMachina. She is an analogue of Factory Girl from Ruby.



What is this?



As just mentioned, ExMachina is designed in the image of Factory Girl - the implementation of the Ruby Factory pattern (also from great guys from Thoughtbot). We proceed from the fact that it would be great to add various models with links to tests, without rewriting the code from time to time to create them. You can accomplish the same thing yourself with the help of auxiliary modules that include simple functions for generating models. But then everything will be reduced to the constant creation of such modules for each necessary data set, for each connection, and so on. It certainly will have time to get bored.



Getting started



Let's start by opening the mix.exs file to add ExMachina to the deps and application lists. To do this, simply insert another entry for ExMachina into the list of dependencies immediately after ComeOnIn:



 defp deps do [{:phoenix, "~> 1.2.0"}, {:phoenix_pubsub, "~> 1.0"}, {:phoenix_ecto, "~> 3.0"}, {:postgrex, ">= 0.0.0"}, {:phoenix_html, "~> 2.6"}, {:phoenix_live_reload, "~> 1.0", only: :dev}, {:gettext, "~> 0.11"}, {:cowboy, "~> 1.0"}, {:comeonin, "~> 2.5.2"}, {:ex_machina, "~> 1.0"}] end 


And then add :ex_machina to the list of used applications:



 def application do [mod: {Pxblog, []}, applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext, :phoenix_ecto, :postgrex, :comeonin, :ex_machina]] end 


Run the following command to ensure that the application components are ready and configured correctly:



 $ mix do deps.get, compile 


If everything goes well, then you should see a message on the output about installing ExMachina and successfully compiling the project! Before we change the code, you need to run the mix test and make sure that all tests are green.



Add the first role factory.



We need to create a factory module and make it available for all tests. I prefer to do this without inflating the tests. To do this, simply throw the module file with the factories into the test/support directory and then write its import in the tests we need.



So let's start by creating the test/support/factory.ex file:



 defmodule Pxblog.Factory do use ExMachina.Ecto, repo: Pxblog.Repo alias Pxblog.Role alias Pxblog.User alias Pxblog.Post def role_factory do %Role{ name: sequence(:name, &"Test Role #{&1}"), admin: false } end end 


We called it Factory because such a name reflects the essence of this module. Then we will use special factory functions. They compare with the sample an atom supplied to the input, which determines which type of factory to assemble / create . Since this library is pretty close to Factory Girl, it also brings with it some naming conventions that are important to know. The first such name will be build . The build function means that the model ( not the revision ) will be built without saving to the database. The second agreement will be the name of the insert function, which vice versa saves the model in the database, thereby creating it.



We also need to specify use ExMachina.Ecto so that ExMachina uses Ecto as the Repo layer and behaves accordingly when creating models, associations, etc. We also need to add pseudonyms to all the models for which we will write factories.



The role_factory function should simply return a Role structure that defines default properties. This feature only supports arity 1.



The piece with the sequence function is pretty curious. We need to generate a unique name for each role. Therefore, we will make it sequentially generated. To do this, we take the function sequence , in which we pass two arguments: the first is the name of the field for which we want to generate a sequence, the second is an anonymous function, which returns a string and interpolates the value inside it. Let's take a look at this feature:



 &”Test Role #{&1}” 


If you are familiar with Elixir, you may have learned an alternative way to write anonymous functions. It roughly translates as:



 fn x -> "Test Role #{x}" end 


So the sequence function can be explained this way:



 sequence(:name, fn x -> "Test Role #{x}" end) 


Finally, set the admin flag to false , because we use this value as the default condition. We can create the administrative role by specifying this explicitly. Other more advanced features of ExMachina let's discuss a little later. Now we will spend some time combining our new factory Role with controller tests.



Add Role factory to controller tests



First open the test/controllers/user_controller_test.exs . At the top, in the setup block, add the use of our new function TestHelper.create_role :



 # ... import Pxblog.Factory @valid_create_attrs %{email: "test@test.com", username: "test", password: "test", password_confirmation: "test"} @valid_attrs %{email: "test@test.com", username: "test"} @invalid_attrs %{} setup do user_role = insert(:role) {:ok, nonadmin_user} = TestHelper.create_user(user_role, %{email: "nonadmin@test.com", username: "nonadmin", password: "test", password_confirmation: "test"}) admin_role = insert(:role, admin: true) {:ok, admin_user} = TestHelper.create_user(admin_role, %{email: "admin@test.com", username: "admin", password: "test", password_confirmation: "test"}) {:ok, conn: build_conn(), admin_role: admin_role, user_role: user_role, nonadmin_user: nonadmin_user, admin_user: admin_user} end # ... 


But before that, we import the factory module itself. In line 10, we simply add the role using the :role factory. In line 13, we do the same, but override the admin flag to true .



Save the file and restart the tests. All of them must still pass! Now let's write a factory for users, which also creates connections.



Add a factory for users



Take a look at the factory for users.



 def user_factory do %User{ username: sequence(:username, &"User #{&1}"), email: "test@test.com", password: "test1234", password_confirmation: "test1234", password_digest: Comeonin.Bcrypt.hashpwsalt("test1234"), role: build(:role) } end 


Basically, this factory is the same as what we wrote earlier for creating roles. But there are a couple of pitfalls with which we have to deal. Above, on line 7 , you can see that we set the password_digest value to the password password hash value (as we simulate user login, we need to add this as well). We simply call the Bcrypt module from Comeonin and use the hashpwsalt function, passing the same value to it as in the password / password_confirmation fields. On the next line, we also set role as an association. We use the build function and pass in it the name of the association we want to build, in the form of an atom.



After modifying the user factory, let's go back to the file test/controllers/user_controller_test.exs .



 setup do user_role = insert(:role) nonadmin_user = insert(:user, role: user_role) admin_role = insert(:role, admin: true) admin_user = insert(:user, role: admin_role) {:ok, conn: build_conn(), admin_role: admin_role, user_role: user_role, nonadmin_user: nonadmin_user, admin_user: admin_user} end 


Now we will finally replace all calls to TestHelper calls to the factory. We take the role and transfer it to the factory to create a user with the right role. Then, do the same with the administrator, but we don’t need to change our tests!



Run them and make sure they are still green. We can continue.



Add factory for posts



I think we have already filled our hands in adding new factories, so work on the latter should not cause any difficulties.



There is nothing new here, so let's just change the file test/controllers/post_controller_test.exs :



 def post_factory do %Post{ title: "Some Post", body: "And the body of some post", user: build(:user) } end 


Once again, we import the Pxblog.Factory module Pxblog.Factory that our tests know where the factory is to which we send calls. Then we replace all the steps to create a post in the setup block with a factory call. Using the insert function, a role structure is created, which is then used to create a user through the factory, which is finally used to create the post associated with it ... Just something!



Run the tests. They turned green again!



From this place, everything else is just extra work. Let's go back and replace all calls to TestHelper with Factory calls. This is not particularly new or exciting, so I will not pay undue attention to explaining the details.



Other ways to connect factories



I choose the path of explicitly connecting my factories to each of the tests, but if you don’t want to do the same, you can use one of the following methods.



Add an alias to the using block in the test/support/model_case.ex :



 using do quote do alias Pxblog.Repo import Ecto import Ecto.Changeset import Ecto.Query import Pxblog.ModelCase import Pxblog.Factory end end 


And the test/support/conn_case.ex :



 using do quote do # Import conveniences for testing with connections use Phoenix.ConnTest alias Pxblog.Repo import Ecto import Ecto.Changeset import Ecto.Query import Pxblog.Router.Helpers import Pxblog.Factory # The default endpoint for testing @endpoint Pxblog.Endpoint end end 


Other ExMachina features



For the purposes of a small blogging engine, we do not need any other features provided by ExMachina. For example, besides build and create there is support for some other functions for the sake of convenience (I use build as an example, but it also works with create ):



 build_pair(:factory, attrs) <- Builds 2 models build_list(n, :factory, attrs) <- Builds N models 


You can also save the model you built using the build method by calling create on it:



 build(:role) |> insert 


Other resources



For more information on using ExMachina, visit the Github page . You can also visit the Thoughbot technical blog, where the creators posted a wonderful ExMachina announcement and some other ways to use it.



Let's sum up



At first, I must say, I was a little wary, remembering how I had previously implemented some things with the help of Factory Girl. I was afraid that here everything would go the same way. But Elixir protects us from ourselves, which helps to find a balance when testing. The syntax is clear and clean. The amount of code required has decreased significantly. Many thanks to the glorious guys from Thoughtbot for another extremely useful library.



Conclusion from the Wuns



Today is a very short conclusion - just subscribe to our Elixir community and get interesting articles in Russian every week.



Other series articles



  1. Introduction
  2. Authorization
  3. Adding Roles
  4. Process roles in controllers
  5. We connect ExMachina
  6. Markdown support
  7. Add comments
  8. We finish with comments
  9. Channels
  10. Channel testing
  11. Conclusion




Good luck in learning, stay with us!

Source: https://habr.com/ru/post/316996/



All Articles