Skip to contents

Motivation

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.R
mod_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.R
mod_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.R
mod_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.R
mod_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.R
mod_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.R
mod_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.R
mod_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.R
mod_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.R
mod_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.R
compare_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.R
create_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:

  • InitialData and ChangedData (from data/) are the base and compare data for the select and compare tests.
  • Roster2021/Roster2022 are the base and compare data for the compare_data(), compare_summary(), and create_comparison_report() tests.
  • inst/extdata/csv/InitialData.csv and inst/extdata/csv/ChangedData.csv are 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: