es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Burlando llamadas consecutivas de funciones en Elixir con Mock o Mox

Estoy intentando simular múltiples llamadas a una función para que devuelva un valor específico diferente cada vez. No estoy tan familiarizado/a con Elixir ni con los conceptos funcionales.

defmodule Roller do
  def roll do
      1..10
      |> Enum.random()
  end
end

Roller devuelve un número aleatorio en cada llamada.

defmodule VolunteerFinder do
  import Roller

  def find(list) do
    find(list, [])
  end

  defp find([], result) do
    result
  end

  defp find([head | tail], result) do
    result = [%{name: head, score: Roller.roll()} | result]
    find(tail, result)
  end
end

Así que, suponiendo que la lista contenga más de un elemento, el roller se llama 2 veces. En mi prueba, necesito controlarlo de alguna manera.

He intentado hacerlo con Mock. Me gustaría hacer algo como esto de la manera más simple posible. Sería genial no tener que guardar ningún estado en ningún lugar ni ejecutar procesos separados para cada llamada. Sé que el pensamiento de Elixir puede ser un poco diferente al paradigma objetivo que tengo. ¿Cuál es la forma más correcta de Elixir de probar el módulo VolunteerFinder?

defmodule VolunteerFinderTest do
  use ExUnit.Case
  import Mock
  import Roller

  test "Encontrar voluntario para una lista con un elemento" do
    with_mock("Roller",
      Roller,
      [roll: fn() -> 5 end]
    ) do
      assert VolunteerFinder.find(["John"]) == [%{name: "John", score: 5}]
    end
  end

  test "Encontrar voluntario para una lista con dos elementos" do
    with_mock("Roller",
      Roller,
      [roll: fn
        () -> 2
        () -> 5
      end]
    ) do
      assert VolunteerFinder.find(["John", "Andrew"])
      == [%{name: "Andrew", score: 5}, %{name: "John", score: 2}]
    end
  end
end
Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    Para simular funciones en Elixir, puedes usar el módulo Mock que es proporcionado por la biblioteca estándar. El módulo Mock te permite crear una implementación falsa de una función que se puede utilizar en tus pruebas en lugar de la implementación real.

    Para simular una función que devuelve un valor diferente en cada llamada, puedes usar la función calls() en la definición de la simulación. La función calls() toma una lista de tuplas donde cada tupla contiene los argumentos que se pasaron a la función y el valor que debe devolverse para esos argumentos.

    Aquí hay una versión actualizada del módulo VolunteerFinderTest que utiliza una implementación simulada de la función Roller.roll():

    defmodule VolunteerFinderTest do
      use ExUnit.Case
    
      import Mock
    
      describe "find/1" do
        test "returns volunteer with a score of 5 for a list with one element" do
          mock = mock(Roller, [:roll])
          expect(mock, :roll, fn() -> 5 end)
    
          assert VolunteerFinder.find(["John"]) == [%{name: "John", score: 5}]
          assert_received {mock, :roll, []}
        end
    
        test "returns volunteers with different scores for a list with two elements" do
          mock = mock(Roller, [:roll])
          expect(mock, :roll, fn() ->
            Enum.take([2, 5, 9], 2) # return 2 on first call, then 5 on the second
          end)
    
          assert VolunteerFinder.find(["John", "Andrew"])
          == [%{name: "Andrew", score: 5}, %{name: "John", score: 2}]
          assert_received {mock, :roll, []}, times: 2
        end
      end
    end
    

    En este ejemplo, creamos una simulación del módulo Roller y especificamos que queremos simular la función roll() pasando [:roll] como segundo argumento a la función mock(). Luego usamos la función expect() para definir el comportamiento de la función simulada.

    En la primera prueba, usamos fn() -> 5 end para especificar que la función simulada roll() siempre debe devolver 5. En la segunda prueba, usamos fn() -> Enum.take([2, 5, 9], 2) end para especificar que la función simulada roll() debe devolver 2 en la primera llamada y 5 en la segunda llamada.

    Después de definir la implementación simulada, podemos llamar a la función find() en VolunteerFinder y verificar que devuelva lo que esperamos. También usamos la función assert_received() para asegurarnos de que la función simulada roll() haya sido llamada con los argumentos correctos. Pasamos times: 2 como opción a assert_received() en la segunda prueba porque la función find() llama a roll() dos veces.

Comments are closed.