tests
tests.RmdMotivation
dfdiffs has two kinds of code to test: the Shiny modules
(upload, select, and compare) and the base-R comparison engine. The
modules keep their reactive logic in server functions, which are tested
with shiny::testServer(). testServer() runs a
module’s server function without starting an app or a browser. Each test
sets inputs with session$setInputs(), then checks the
reactive values (or rendered outputs) the module returns.
The comparison engine (compare_values(),
diff_values_by_key(), and the join helpers) is tested
directly with plain test_that() blocks, so no Shiny session
is needed.
The tests live in tests/testthat/, and we can run the
full suite with devtools::test():
devtools::test()There are currently 10 test files containing 48 tests
(test_that() blocks) and 118 expectations.
Test inventory
Each row is one test_that() block, except the last few
rows, which each summarize several tests from one file (the row says how
many). The first column gives the file and the test name (the string
passed to test_that()). The second column names the app
feature or function the test protects, and the third describes what the
test does and what it expects.
| Test name / file | Feature | Test |
|---|---|---|
test-mod_upload.Rmod_upload_server() reads an uploaded base file into base_data() |
Upload module: base file | Uploads InitialData.csv as
base_file (with base_new_name = "InitialData")
and expects base_data() to be a data frame with 5 rows and
a subject_id column. |
test-mod_upload.Rmod_upload_server() reads an uploaded compare file into comp_data() |
Upload module: compare file | Uploads ChangedData.csv as
comp_file (with comp_new_name = "ChangedData")
and expects comp_data() to be a data frame with 5 rows and
a subject_id column. |
test-mod_upload.Rmod_upload_server() reads an uploaded xlsx sheet into base_data() |
Upload module: xlsx file | Writes InitialData to a temp
.xlsx file with openxlsx::write.xlsx(),
uploads it as base_file with a sheet name, and expects
base_data() to match InitialData’s row count
and columns. |
test-mod_upload.Rmod_upload_server() uses base_new_name for base_name() when supplied |
Upload module: dataset naming | Uploads InitialData.csv with
base_new_name = "InitialData" and expects
base_name() to return "InitialData". |
test-mod_select.Rmod_select_server() builds joined base/compare data from selected columns |
Select module: join column | Passes InitialData and
ChangedData in as the uploaded data, selects every column,
and sets by = c("subject_id", "record"). Expects
base_join_col_data() and comp_join_col_data()
to be data frames with the same number of rows as their source data,
containing join_column, join_source, and
data_source columns (with data_source equal to
the dataset names). |
test-mod_select.Rmod_select_server() falls back to a row-by-row comparison with no ‘by’ columns |
Select module: row-by-row comparison | Selects every column but leaves by empty.
Expects base_join_col_data() to have no
join_column, but to keep the data_source
column. |
test-mod_compare.Rmod_compare_server() detects changed values via the ‘join_column’ path |
Compare module: changed data | Builds joined base/compare data with
create_join_column() (on subject_id and
record) and passes it in as the selected data. Expects
compare_cols() to include join_column,
changed_data() to contain diffs and
diffs_byvar (with at least one row in
diffs_byvar), and new_data() and
deleted_data() to have 0 rows. |
test-mod_compare.Rmod_compare_server() download report works via the ‘join_column’ path |
Compare module: report download | With joined base/compare data as the selected data,
reads the downloaded .xlsx file and expects it to contain
the “New Data”, “Deleted Data”, “Changed Data”, and “Review Changes”
sheets, with the “Review Changes” sheet including a
join_column. |
test-mod_compare.Rmod_compare_server() download report works via the row-by-row path |
Compare module: report download (no join) | With no join column in the selected data, expects
compare_cols() to exclude join_column, and the
downloaded report’s “Review Changes” sheet to include a
rownumber column instead. |
test-compare_data.Rcompare_data() returns new, deleted, and changed comparison tables |
compare_data() |
Runs compare_data() on
Roster2021/Roster2022 with
by = "subject_id" and expects a named list of
new_data, deleted_data,
changed_num_diffs, changed_var_diffs,
changed_class_diffs, column_diffs, with
new_data/deleted_data non-empty data frames
and column_diffs a dfdiffs_column_diff
object. |
test-create_comparison_report.Rcreate_comparison_report() writes all nine sheets to the xlsx file |
create_comparison_report() |
Runs create_comparison_report() on
Roster2021/Roster2022 and expects the output
file to exist with all nine expected sheets, the “New Data” sheet to be
non-empty, the “Column Diffs” sheet to mark every shared column
“common”, and the “Summary” sheet to include n_base. |
test-launch_app.R(four tests, one per launcher) |
App launchers | Calls launch_app(),
launch_app_dev(), launch_upload_demo(), and
launch_select_demo() and expects each to return a
shiny.appobj without error. |
test-join_helpers.R(seven tests) |
select_cols(),
anti_join_base(), left_join_base()
|
Covers column subsetting (including a missing-column
error), multi-column-key matching, NA-in-key handling, and
left-join column attachment/NA fill for unmatched
rows. |
test-compare_values.R(nineteen tests) |
compare_values(),
diff_values_by_key()
|
Covers NA-vs-NA and
NA-vs-value handling, exact string comparison, numeric
tolerance (default and custom
tolerance/scale), integer/double comparison,
Date vs. same-instant POSIXct (equal) and
different POSIXct (not equal), factor/character label
comparison (default and strict_factor = TRUE),
ignore_case/trim_ws (including on factor
labels), vectorization, and diff_values_by_key()’s
row-matching, zero-diff, class-mismatch, duplicate-key-warning, and
ignore_case/trim_ws pass-through
behavior. |
test-compare_columns.R(three tests) |
compare_columns() |
Covers base-only/compare-only/common column reporting,
the all-common case, and a print.dfdiffs_column_diff()
smoke test. |
test-compare_summary.R(four tests) |
compare_summary() |
Covers row/column counts against the
Roster2021/Roster2022 fixture,
base-only/compare-only column counts, value-diff/class-mismatch counts,
and a print.dfdiffs_summary() smoke test. |
Test data
The tests use the package’s own datasets, so they don’t depend on any
files outside of dfdiffs:
-
InitialDataandChangedData(fromdata/) are the base and compare data for the select and compare tests. -
Roster2021/Roster2022are the base and compare data for thecompare_data(),compare_summary(), andcreate_comparison_report()tests. -
inst/extdata/csv/InitialData.csvandinst/extdata/csv/ChangedData.csvare the files uploaded in the CSV upload tests.
Call structure
Each Shiny module test targets a module’s server function, but those
server functions call other dfdiffs functions along the
way. The call trees below show the functions each module test exercises
indirectly. For example, the compare module tests also run
create_new_data(), create_deleted_data(), and
create_modified_data().
The trees were generated from the package source with stackcallr
(pak::pak("mjfrigaard/stackcallr")). Only functions defined
in dfdiffs are shown.
library(stackcallr)
call_tree_dir("R", root = "mod_upload_server")
call_tree_dir("R", root = "mod_select_server")
call_tree_dir("R", root = "mod_compare_server")█─mod_upload_server
├─█─upload_data
│ └─load_flat_file
├─base_react_theme
└─comp_react_theme
█─mod_select_server
├─select_cols
├─base_react_theme
├─comp_react_theme
├─info_react_theme
└─█─create_join_column
└─select_cols
█─mod_compare_server
├─compare_columns
├─%nin%
├─info_react_theme
├─█─create_new_data
│ ├─anti_join_base
│ ├─select_cols
│ ├─rename_join_col
│ └─create_new_column
├─new_react_theme
├─█─create_deleted_data
│ ├─anti_join_base
│ ├─select_cols
│ ├─rename_join_col
│ └─create_new_column
├─deleted_react_theme
├─█─create_modified_data
│ ├─select_cols
│ ├─█─diff_values_by_key
│ │ ├─compare_values
│ │ └─class_diffs
│ ├─rename_join_col
│ ├─create_new_column
│ └─class_diffs
├─select_cols
├─changed_react_theme
├─left_join_base
└─█─create_comparison_report
├─█─create_new_data
│ ├─anti_join_base
│ ├─select_cols
│ ├─rename_join_col
│ └─create_new_column
├─█─create_deleted_data
│ ├─anti_join_base
│ ├─select_cols
│ ├─rename_join_col
│ └─create_new_column
├─█─create_modified_data
│ ├─select_cols
│ ├─█─diff_values_by_key
│ │ ├─compare_values
│ │ └─class_diffs
│ ├─rename_join_col
│ ├─create_new_column
│ └─class_diffs
├─create_empty_tbl
├─compare_columns
├─class_diffs
└─█─compare_summary
└─█─compare_data
├─█─create_new_data
│ ├─anti_join_base
│ ├─select_cols
│ ├─rename_join_col
│ └─create_new_column
├─█─create_deleted_data
│ ├─anti_join_base
│ ├─select_cols
│ ├─rename_join_col
│ └─create_new_column
├─█─create_changed_data
│ ├─select_cols
│ ├─█─diff_values_by_key
│ │ ├─compare_values
│ │ └─class_diffs
│ ├─rename_join_col
│ ├─create_new_column
│ └─class_diffs
├─class_diffs
└─compare_columns
The download-handler tests for mod_compare_server() also
exercise create_comparison_report() (shown as a subtree
above), because the module calls it instead of building the report
itself.
The remaining test files (test-compare_data.R,
test-create_comparison_report.R,
test-join_helpers.R, test-compare_values.R,
test-compare_columns.R, and
test-compare_summary.R) call their target functions
directly instead of going through a Shiny module. Their call trees are
already covered in the getting-started and compare-data vignettes.
What isn’t tested
The current tests cover the reactive logic of the three modules, the
base-R comparison engine, compare_data(),
compare_columns(), compare_summary(), and
create_comparison_report(). They also include a build smoke
test for every launch_*() entry point. The following areas
don’t have tests yet:
- The UI functions (
mod_upload_ui(),mod_select_ui(),mod_compare_ui(), andapp_ui()). -
cross_tabyl(), and a standalone (non-module) test ofcreate_join_column(). It’s only exercised indirectly, through the select and compare module tests. -
class_diffs()beyond the one genuine-class-mismatch case covered intest-compare_values.R.