Phoenix Elixir: mock internal functions - testing

I'm currently testing controller that uses the function create_zone that depends on a function that retrieves a user to associates said user to a zone and then creates a participant entry that is only an association table of both entries.
def create_zone(attrs \\ %{}, user_id) do
user = Accounts.get_user!(user_id)
with{:ok, %Zone{} = zone} <- %Zone{}
|> Zone.changeset(attrs,user)
|> Repo.insert()
do
create_participant(zone,user)
end
end
And I would like to test it using ExUnit but the problem is that the testing framework tries to search a non existent record in the database.
** (Ecto.NoResultsError) expected at least one result but got none in query:
from u in Module.Accounts.User,
where: u.id == ^1
How could I mock or create it just for testing purposes?

Don't mock it, create it with ex_machina: https://github.com/thoughtbot/ex_machina
Mocking is discouraged in Elixir: http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-contracts/ (you don't really need to read it now, but in case you are want to mock some external resource, read it).

You can write a simple factory module that uses Ecto to insert into the database. The test will be wrapped in a database transaction and rolled back automatically thanks to the Ecto.Sandbox.
defmodule Factory do
def create(User) do
%User{
name: "A. User",
email: "user_#{:rand.uniform(10000)}#mail.com"
}
end
def create(Zone) do
%Zone{
# ... random / default zone attributes here...
}
end
def create(schema, attrs) do
schema
|> create()
|> struct(attributes)
end
def insert(schema, attrs \\ []) do
Repo.insert!(create(schema, attrs))
end
end
Then in your test custom attributes are merged with the factory defaults, including associations.
test "A test" do
user = Factory.insert(User, name: "User A")
zone = Zones.create_zone(user.id)
assert zone
end
See chapter 7 of what's new in ecto 2.1 for a more detailed explanation.

Related

Override default current user to Guardian current user with Canary

I am trying to implement Canary into my application, and I have come across a problem. The docs (https://github.com/cpjk/canary#overriding-the-default-user) say that I need to have an Ecto record for the current user in conn.assigns.current_user. Since I am using Guardian, my current user is stored in Guardian.Plug.current_resource(conn). What is the best way to let canary know that this is where I store my current user?
I can't use the config :canary, current_user: :my_user_key because it isn't even in conn.assigns.
Help is appreciated!
Following this article, you should create a Plug for that:
defmodule MyApp.Plug.CurrentUser do
def init(opts), do: opts
def call(conn, _opts) do
current_user = Guardian.Plug.current_resource(conn)
Plug.Conn.assign(conn, :current_user, current_user)
end
end
and put it in a router pipeline:
pipeline :require_login do
plug Guardian.Plug.EnsureAuthenticated, handler: MyApp.GuardianErrorHandler
plug MyApp.Plug.CurrentUser
end

Testing Elixir/Phoenix Service modules

I've been playing around with Elixir/Phoenix third-party modules. ( Modules that are used to fetch some data from a 3rd party service ) One of those module looking like so:
module TwitterService do
#twitter_url "https://api.twitter.com/1.1"
def fetch_tweets(user) do
# The actual code to fetch tweets
HTTPoison.get(#twitter_url)
|> process_response
end
def process_response({:ok, resp}) do
{:ok, Poison.decode! resp}
end
def process_response(_fail), do: {:ok, []}
end
The actual data doesn't matter in my question. So now, I'm interested in how can I dynamically configure the #twitter_url module variable in tests to make some of the tests fail on purpose. For example:
module TwitterServiceTest
test "Module returns {:ok, []} when Twitter API isn't available"
# I'd like this to be possible ( coming from the world of Rails )
TwitterService.configure(:twitter_url, "new_value") # This line isn't possible
# Now the TwiterService shouldn't get anything from the url
tweets = TwitterService.fetch_tweets("test")
assert {:ok, []} = tweets
end
end
How can I achieve this?
Note: I know I can use :configs to configure #twiter_url separately in dev and test environments, but I'd like to be able to test on a real response from the Twitter API too, and that would change the URL on the entire Test environment.
One of the solutions that I came up with was
def fetch_tweets(user, opts \\ []) do
_fetch_tweets(user, opts[:fail_on_test] || false)
end
defp _fetch_tweets(user, [fail_on_test: true]) do
# Fails
end
defp _fetch_tweets(user, [fail_on_test: false]) do
# Normal fetching
end
But that just seems hackish and silly, there must be a better solution to this.
As it was suggested by José in Mocks And Explicit Contracts, the best way would be probably to use a dependency injection:
module TwitterService do
#twitter_url "https://api.twitter.com/1.1"
def fetch_tweets(user, service_url \\ #twitter_url) do
# The actual code to fetch tweets
service_url
|> HTTPoison.get()
|> process_response
end
...
end
Now in tests you just inject another dependency when necessary:
# to test against real service
fetch_tweets(user)
# to test against mocked service
fetch_tweets(user, SOME_MOCK_URL)
This approach will also make it easier to plug in different service in the future. The processor implementation should not depend on it’s underlying service, assuming the service follows some contract (responds with json given a url in such a particular case.)
config sounds like a good way here. You can modify the value in the config at runtime in your test and then restore it after the test.
First, in your actual code, instead of #twitter_url, use Application.get_env(:my_app, :twitter_url).
Then, in your tests, you can use a wrapper function like this:
def with_twitter_url(new_twitter_url, func) do
old_twitter_url = Application.get_env(:my_app, :twitter_url)
Application.set_env(:my_app, :twitter_url, new_twitter_url)
func.()
Application.set_env(:my_app, :twitter_url, old_twitter_url)
end
Now in your tests, do:
with_twitter_url "<new url>", fn ->
# All calls to your module here will use the new url.
end
Make sure you're not using async tests for this as this technique modifies global environment.

Rails 3 ActionMailer class loaded but methods not available in controller

Many simmilar Q/A on this topic here and there, but I was unable to find exact solution for my problem. Using Rails 3.0.9 now, and trying to upgrade existing older application(not Rails).
The goal is to send simple email to new clients created by admins.
Have been following this oficial guide (and many others), but with no success.
The issue is, that method(s) defined in this controller, from class 'UserMailer', aren`t recognised from another controller, while class 'UserMailer' itself recognised is(how do I know this, will be explained below):
/app/mailers/user_mailer.rb
class UserMailer < ActionMailer::Base
default :from => "info#xxxxx.sk"
def kokotina # << this is just a dummy method for testing
caf = "ssss"
end
def regMailUsr(nazov, priezvisko, email, pass)
#nazov = nazov
#priezvisko = priezvisko
#email = email
#pass = pass
#url = "http://loyalty2.xxxx.sk"
mail(to: email, subject: 'Vaša registrácia bola dokončená.')
end
end
I have also created View for this mail controller but that is not important right now.
The fragments from clients controller are here:
/app/controllers/clients_controller.rb
# encoding: UTF-8
class ClientsController < ApplicationController
load_and_authorize_resource
.......
def new
#noveHeslo = genHeslo(10) # << I defined this in application_controller.rb and it works
UserMailer.kokotina # << just a dummy method from UserMailer
#client = Client.new(params[:client])
.......
end
.......
def create
.......
if #client.save
#send email to new client:
UserMailer.regMailUsr(params[:client][:fname], params[:client][:lname], params[:client][:email], params[:client][:password]).deliver
.....
end ......
Now how do I know that my class is loaded? If in client controller, I change 'UserMailer' to 'xUserMailer', I will get 'no class or method in ...' error, but without 'x', I get only:
'undefined method `kokotina' for UserMailer:Class'
I also tried to define my methods in UserMailer:Class like this:
def self.kokotina # << this is just a dummy method for testing
caf = "ssss"
end
#or even like this
def self <<
def kokotina # << this is just a dummy method for testing
caf = "ssss"
end
end
#and then tried to invoke this method(s) like this:
UserMailer.new.kokotina
#or simply
kokotina
Strange is, that when I put contents of file '/app/mailers/user_mailer.rb' at the end of 'application_helper.rb' file, just after the end of 'module ApplicationHelper', I get no errors but of course, it won`t work.
Please keep in mind that I have no problem coding in another languages, but this mystic/kryptic rules of Ruby on Rails are still a complete mistery to me and unfortunatelly, I don`t have time or even motivation to read time extensive quides or even books for RoR beginners. I have been coding much more difficult applications and implementations, but this heavily discriminating system is driving me nuts.
Thank you all!
Problem solved!
The trick was, that in '/app/mailers/user_mailer.rb', I had multibyte characters. In mail subject.
So I added:
# encoding: UTF-8
at the very first line of '/app/mailers/user_mailer.rb'
I found this by total accident: later my rails app could not start, and server was simply throwing HTTP 500 error. So no trace, error defining etc.
I found out that multibyte string in:
mail(to: email, subject: 'Vaša registrácia bola dokončená.')
Was responsible for crash. When I removed that string, I noticed one important side effect: my methods became magicaly available for another controller!!!!
So if someone could give me at least one reason to lowe Rails...

Do I use Option as result when fetching an object from the database with an Id?

I have made a definition which fetches a user from the database.
def user(userId: Int) : User = database withSession {
(for{
u <- Users if u.id === userId}
yield u).first
}
Potetially the database could return an empty list if used with an non existing userId.
However I can't see when a non existing userId would be provided. For example my userId is fetched from the logged in user. And if a non existing userId is provided then I think it's ok to fail the request hard.
Any thoughts?
No it's not ok to fail the request hard :
def user(userId: Int) : Option[User] // is OK
def user(userId: Int) : Either[String,User] // is OK
def user(usedId: Int) : User // is not OK
or else you could create a type (a concept) which encapsulate an Integer which make sure it's a valid UserId (at birthing).
sealed case class UserId(u:Int) //extends AnyVal // If it's scala 2.10.0
object UserId {
def get(i:Int) : Option[UserId] = //some validation
} /// ....
def user(userId:UserId) : User //is OK // well it depends on the semantic of user destruction.
When you make a def, you must make sure there is a proper relation between the domain (this and args) of your function and the codomain (result).
Anyways, do not hesitate to type (create concepts), it will help you to reason about your code.
Why def user(userId: Int) :User is not Ok ?
Because a relation between the elements of Integer to the elements of User doesn't exist. What if UserIds are all positive integers, but you ask for user(-10) ? (it won't happen, right ?) Should this call raise an exception ? Or return null ?
If you think it should return null, then return an Option, it encapsulates the potential missing correspondance.
If you think it should raise an exception, then return :
a Validation[SomethingRepresentingAnError, User] (scalaz),
an Either[SomethingRepresentingAnError, User] (scala 2.7, 2.8, 2.9)
or a Try[User] (scala 2.10)
Having rich return types will help you to use your API correctly.
Btw Scala doesn't use checked exception, so you cannot use exception as an alternative result. Exception should be keept for truly exceptional behaviour (as Runtime Exceptions).
See also :
http://www.scala-lang.org/api/current/index.html#scala.util.control.Exception$
I think it's always good idea to return Option[] when fetching data by id. You can not be sure that user with such id exist. E. g. another request has deleted this user or somebody was trying to tamper with your input data. Database is an external system to your application and if you know how to recover from such failures then you should do it. Especially in Scala where Option is a good tool for such task.
Option is the most minimalistic way to represent the return value from some computation that may fail. Throwing exceptions or returning null are acceptable only when dealing with Java code and your hands are somehow tied by an existing API (and when you're code is being called from Java code).
The next step up from Option would be Either[FailureIndication, SuccessValue].
A further improvement is ScalaZ's Validation.

How to get SQL queries for each user where env is production

I’m developing an application dedicated to generate statistical reports, I would like that user after saving their stat report they save sql queries too. To do that I wrote the following module:
module SqlHunter
class ActiveRecord::ConnectionAdapters::AbstractAdapter
##queries = []
cattr_accessor :queries
def log_info_with_trace(sql, name, runtime)
return unless #logger && #logger.debug?
##queries << sql
end
alias_method_chain :log_info, :trace
end
end
in the controller I wrote that
sqlfile = File.open("public/advancedStats/#{#dir_name}/advancedStatQuery.sql", 'w')
#queries = ActiveRecord::ConnectionAdapters::AbstractAdapter::queries
for query in #queries do
sqlfile.write("#{query} \n")
end
sqlfile.close
I also modified Rails environment by adding this line:
ActiveRecord::Base.logger.level = Logger::DEBUG
This program is working and I can get all queries but, I need only the specific queries done by one user to generate a stat report.
Is someone has any idea,
Thanks,
mgatri
You could add an accessor that says if you wish to log or not.
##queries = []
##loging = false
cattr_accessor :queries, :logging
def log_info_with_trace(sql, name, runtime)
return unless #logger && #logger.debug?
##queries << sql if ##logging
end
When you do a ActiveRecord::ConnectionAdapters::AbstractAdapter::logging = true
Then, your SQL queries will be logged. When you set it to false, they won't.
So you can log them only whenever you want.
To erase the old queries, you just need to clear the array.
ActiveRecord::ConnectionAdapters::AbstractAdapter::queries.clear