Skip to content

Commit c89a28a

Browse files
Property-based testing with Stream Data for VoteCalculator
1 parent cd6490b commit c89a28a

File tree

15 files changed

+97
-34
lines changed

15 files changed

+97
-34
lines changed

.formatter.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[
2-
import_deps: [:ecto, :phoenix],
2+
import_deps: [:ecto, :phoenix, :stream_data],
33
inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"],
44
subdirectories: ["priv/*/migrations"]
55
]

config/cards.exs

+1-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ config :pointing_party,
1515
Update the application to save an individual's vote and the final card points to the database.
1616
"""
1717
},
18-
1918
%{
2019
title: "Add Guardian dependency",
2120
description: """
@@ -34,5 +33,5 @@ config :pointing_party,
3433
description: """
3534
Update our existing authentication flow to use the newly created Auth module.
3635
"""
37-
},
36+
}
3837
]

lib/pointing_party/account.ex

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ defmodule PointingParty.Account do
1111

1212
def create(attrs) do
1313
changeset = changeset(%Account{}, attrs)
14+
1415
if changeset.valid? do
1516
account = apply_changes(changeset)
1617
{:ok, account}
+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
defmodule PointingPartyWeb.Presence do
2-
use Phoenix.Presence, otp_app: :pointing_party,
3-
pubsub_server: PointingParty.PubSub
2+
use Phoenix.Presence,
3+
otp_app: :pointing_party,
4+
pubsub_server: PointingParty.PubSub
45
end

lib/pointing_party_web/channels/room_channel.ex

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ defmodule PointingPartyWeb.RoomChannel do
3232
end
3333

3434
defp initialize_state(%{assigns: %{cards: _cards}} = socket), do: socket
35+
3536
defp initialize_state(socket) do
3637
[first | cards] = Card.cards()
3738

lib/pointing_party_web/controllers/session_controller.ex

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ defmodule PointingPartyWeb.SessionController do
1515
|> put_session(:username, username)
1616
|> redirect(to: "/cards")
1717
|> halt()
18+
1819
{:error, changeset} ->
1920
render(conn, "new.html", changeset: changeset)
2021
end
2122
end
2223

2324
def delete(conn, _params) do
24-
clear_session(conn)
25-
|> redirect(to: "/login") |> halt()
25+
clear_session(conn) |> redirect(to: "/login") |> halt()
2626
end
2727
end

lib/pointing_party_web/plugs/auth.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ defmodule PointingPartyWeb.Plugs.Auth do
1212
end
1313

1414
defp authenticate(conn) do
15-
get_session(conn, :username)
15+
get_session(conn, :username)
1616
end
1717
end

mix.exs

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ defmodule PointingParty.MixProject do
4141
{:gettext, "~> 0.11"},
4242
{:jason, "~> 1.0"},
4343
{:plug_cowboy, "~> 2.0"},
44-
{:ex_machina, "~> 2.3", only: :test}
44+
{:ex_machina, "~> 2.3", only: :test},
45+
{:stream_data, "~> 0.1", only: :test}
4546
]
4647
end
4748
end

mix.lock

+1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@
2121
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
2222
"postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
2323
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
24+
"stream_data": {:hex, :stream_data, "0.4.3", "62aafd870caff0849a5057a7ec270fad0eb86889f4d433b937d996de99e3db25", [:mix], [], "hexpm"},
2425
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
2526
}

priv/repo/migrations/20190708014847_create_cards.exs

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ defmodule PointingParty.Repo.Migrations.CreateCards do
33

44
def change do
55
create table(:cards) do
6-
add :title, :string
7-
add :description, :string
6+
add(:title, :string)
7+
add(:description, :string)
88

99
timestamps()
1010
end
11-
1211
end
1312
end

priv/repo/migrations/20190714140153_add_points_to_cards.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule PointingParty.Repo.Migrations.AddPointsToCards do
33

44
def change do
55
alter table("cards") do
6-
add :points, :integer, default: 0
6+
add(:points, :integer, default: 0)
77
end
88
end
99
end

priv/repo/seeds.exs

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
# We recommend using the bang functions (`insert!`, `update!`
1111
# and so on) as they will fail if something goes wrong.
1212

13-
1413
alias PointingParty.{Card, Repo}
1514

1615
cards = [
@@ -21,7 +20,7 @@ cards = [
2120
%{title: "Fifth card", description: "This is a description of the fifth card."},
2221
%{title: "Sixth card", description: "This is a description of the sixth card."},
2322
%{title: "Seventh card", description: "This is a description of the seventh card."},
24-
%{title: "Eighth card", description: "This is a description of the eighth card."},
23+
%{title: "Eighth card", description: "This is a description of the eighth card."}
2524
]
2625

2726
Enum.each(cards, fn card ->
+76-15
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,84 @@
11
defmodule PointingParty.VoteCalculatorTest do
22
use ExUnit.Case, async: true
3+
use ExUnitProperties
34

4-
@users_with_winner %{
5-
"sean" => %{metas: [%{points: 1}]},
6-
"michael" => %{metas: [%{points: 3}]},
7-
"sophie" => %{metas: [%{points: 3}]}
8-
}
5+
describe "calculate_votes/1" do
6+
setup do
7+
points_map = fixed_map(%{points: integer(1..5)})
98

10-
@users_with_tie %{
11-
"sean" => %{metas: [%{points: 1}]},
12-
"michael" => %{metas: [%{points: 2}]},
13-
"sophie" => %{metas: [%{points: 3}]}
14-
}
9+
metas_map =
10+
fixed_map(%{
11+
metas: list_of(points_map, length: 1)
12+
})
1513

16-
test "calculate_votes/1 calculates when there is a winner" do
17-
{"winner", 3} = PointingParty.VoteCalculator.calculate_votes(@users_with_winner)
18-
end
14+
users = nonempty(map_of(string(:alphanumeric), metas_map))
15+
[users: users]
16+
end
17+
18+
property "winning value is a list or a integer", %{users: users} do
19+
check all users <- users do
20+
{_event, winner} = PointingParty.VoteCalculator.calculate_votes(users)
21+
assert is_list(winner) || is_integer(winner)
22+
end
23+
end
24+
25+
property "tie when winning value is a list, winner when winning value is an integer", %{users: users} do
26+
check all users <- users do
27+
{event, winner} = PointingParty.VoteCalculator.calculate_votes(users)
28+
29+
cond do
30+
is_list(winner) ->
31+
assert event == "tie"
32+
33+
is_integer(winner) ->
34+
assert event == "winner"
35+
end
36+
end
37+
end
38+
39+
property "the winning value is not more than the highest point value", %{users: users} do
40+
check all users <- users do
41+
{_event, winner} = PointingParty.VoteCalculator.calculate_votes(users)
42+
43+
max_vote =
44+
users
45+
|> Enum.map(fn {_username, %{metas: [%{points: points}]}} -> points end)
46+
|> Enum.max()
47+
48+
cond do
49+
is_list(winner) ->
50+
assert Enum.max(winner) <= max_vote
51+
52+
is_integer(winner) ->
53+
assert winner <= max_vote
54+
end
55+
end
56+
end
57+
58+
property "when the winner is a list of two sorted values", %{users: users} do
59+
check all users <- users do
60+
{_event, winner} = PointingParty.VoteCalculator.calculate_votes(users)
61+
62+
if is_list(winner) do
63+
assert length(winner) == 2
64+
65+
votes = Enum.map(users, fn {_username, %{metas: [%{points: points}]}} -> points end)
66+
67+
calculated_votes =
68+
Enum.reduce(votes, %{}, fn vote, acc ->
69+
value = (acc[vote] || 0) + 1
70+
Map.put(acc, vote, value)
71+
end)
72+
73+
sorted =
74+
calculated_votes
75+
|> Enum.sort_by(fn ({_k, v}) -> v end)
76+
|> Enum.map(fn ({a, _b}) -> a end)
77+
|> Enum.take(2)
1978

20-
test "calculate_votes/1 calculates when there is a tie" do
21-
{"tie", [1,2]} = PointingParty.VoteCalculator.calculate_votes(@users_with_tie)
79+
assert sorted == winner
80+
end
81+
end
82+
end
2283
end
2384
end

test/pointing_party_web/controllers/card_controller_test.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule PointingPartyWeb.CardControllerTest do
44
@username "test_user"
55

66
describe "authenticated user" do
7-
setup %{conn: conn} do
7+
setup %{conn: conn} do
88
auth_conn = Plug.Test.init_test_session(conn, username: @username)
99
{:ok, %{conn: auth_conn}}
1010
end

test/pointing_party_web/controllers/page_controller_test.exs

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ defmodule PointingPartyWeb.PageControllerTest do
44
@username "test_user"
55

66
describe "authenticated user" do
7-
setup %{conn: conn} do
8-
conn = conn
9-
|> Plug.Conn.assign(:username, @username)
7+
setup %{conn: conn} do
8+
conn = Plug.Conn.assign(conn, :username, @username)
9+
1010
{:ok, %{conn: conn}}
1111
end
1212

0 commit comments

Comments
 (0)