All Projects → sainathadapa → stubthat

sainathadapa / stubthat

Licence: other
Stubbing framework for R

Programming Languages

r
7636 projects

Projects that are alternatives of or similar to stubthat

PHPUnit-Polyfills
Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests
Stars: ✭ 147 (+764.71%)
Mutual labels:  unit-testing
jest-preset-coffeescript
🃏 Easily write your Jests in @coffeescript.
Stars: ✭ 18 (+5.88%)
Mutual labels:  unit-testing
KrazyKotlin
A collection of useful Kotlin Extension
Stars: ✭ 75 (+341.18%)
Mutual labels:  unit-testing
floss
Unit-testing for those hard to reach places
Stars: ✭ 26 (+52.94%)
Mutual labels:  unit-testing
currency-api
A demo project on how to test a node/express app with Mocha, Nock and proxyquire (MNP) and code coverage with nyc/istanbul.
Stars: ✭ 19 (+11.76%)
Mutual labels:  unit-testing
FakeItEasy.AutoFakeIt
A very simple, yet flexible, "AutoFaker" for FakeItEasy to easily auto generate classes with faked dependencies.
Stars: ✭ 15 (-11.76%)
Mutual labels:  unit-testing
entity-framework-mock
Easy Mock wrapper for mocking EF6 DbContext and DbSet using Moq or NSubstitute
Stars: ✭ 45 (+164.71%)
Mutual labels:  unit-testing
springboot-junit5-mockito2
Show case for how to use junit 5 and mockito 2 for unit testing and integration test in spring boot 2
Stars: ✭ 18 (+5.88%)
Mutual labels:  unit-testing
libcester
A robust header-only unit testing framework for C and C++ programming language. Support function mocking, memory leak detection, crash report.
Stars: ✭ 50 (+194.12%)
Mutual labels:  unit-testing
Swatch
Watcher for Unit Tests written in Swift
Stars: ✭ 55 (+223.53%)
Mutual labels:  unit-testing
tink unittest
Tinkerbell Unit Testing
Stars: ✭ 15 (-11.76%)
Mutual labels:  unit-testing
test-drive
The simple testing framework
Stars: ✭ 37 (+117.65%)
Mutual labels:  unit-testing
goreporter
A Golang tool that does static analysis, unit testing, code review and generate code quality report.
Stars: ✭ 3,019 (+17658.82%)
Mutual labels:  unit-testing
doctest
The fastest feature-rich C++11/14/17/20 single-header testing framework
Stars: ✭ 4,434 (+25982.35%)
Mutual labels:  unit-testing
utest
Lightweight unit testing framework for C/C++ projects. Suitable for embedded devices.
Stars: ✭ 18 (+5.88%)
Mutual labels:  unit-testing
permacoop
Open source and eco design ERP solution reserved for worker-owned business.
Stars: ✭ 167 (+882.35%)
Mutual labels:  unit-testing
NiceDemo
iOS project, built on improved MVP architecture using Coordinator pattern for routing 😎
Stars: ✭ 54 (+217.65%)
Mutual labels:  unit-testing
emacs-jest
A package to run jest inside emacs
Stars: ✭ 74 (+335.29%)
Mutual labels:  unit-testing
tead
Lighting the way to simpler testing
Stars: ✭ 55 (+223.53%)
Mutual labels:  unit-testing
krab
Krab is a migration and automation tool for PostgreSQL based on HCL syntax
Stars: ✭ 15 (-11.76%)
Mutual labels:  unit-testing

stubthat

Join the chat at https://gitter.im/sainathadapa/stubthat

Travis-CI Build Status AppVeyor Build Status codecov.io CRAN version CRAN RStudio mirror downloads

Installation

  • Install the latest stable version from CRAN with
install.packages("stubthat")
  • Install the development version from github with
devtools::install_github("sainathadapa/stubthat")

Introduction

stubthat package provides stubs for use while unit testing in R. The API is highly inspired by Sinon.js. This package is meant to be used along with testthat and mockr packages, specifically the ‘mockr::with_mock’ function.

To understand what a stub is and how they are used while unit testing, please take a look at this Stackoverflow question What is a “Stub”?.

Usage

There are three main steps for creating & using a stub of a function -

  • Invoke the stub function with the function that needs to be mocked
jedi_or_sith <- function(x) return('No one')
jedi_or_sith_stub <- stub(jedi_or_sith)
  • Define the behavior. This is explained in detail in the API section.
jedi_or_sith_stub$withArgs(x = 'Luke')$returns('Jedi')
  • Once the behavior is defined, you can use the stub by calling the jedi_or_sith_stub$f function.
jedi_or_sith('Luke')
#> [1] "No one"
jedi_or_sith_stub$f('Luke')
#> [1] "Jedi"

Use cases

Stubs are generally used in the testing environment. Here is an example:

library(httr) # provides the GET and status_code functions

url_downloader <- function(url) GET(url)

check_api_endpoint_status <- function(url) {
  response <- url_downloader(url)
  response_status <- status_code(response)
  ifelse(response_status == 200, 'up', 'down')
}

This function check_api_endpoint_status should make a GET request (via the url_downloader function) to the specified url (say https://example.com/endpoint) and it should return ‘up’ if the status code is ‘200’. Return ‘down’ otherwise. While testing, it is generally a good idea to avoid making repeated (or any) requests to external sources.

Using stubs (and with_mock from mockr), the above function can be tested without accessing the external source, as shown below:

url_downloader_stub <- stub(url_downloader)
url_downloader_stub$withArgs(url = 'good url')$returns(200)
url_downloader_stub$withArgs(url = 'bad url')$returns(404)

# testthat package provides the expect_equal function
# mockr package provides the with_mock function

check_api_endpoint_status_tester <- function(x) {
  mockr::with_mock(url_downloader = url_downloader_stub$f,
                   check_api_endpoint_status(x))
}

(testthat::expect_equal(check_api_endpoint_status_tester('good url'), 'up'))
#> [1] "up"
(testthat::expect_equal(check_api_endpoint_status_tester('bad url'),  'down'))
#> [1] "down"

Another use case: Consider the outline of a function f1

f1 <- function(...) {
  
  {...some computation...}
  
  interim_val <- f2(...)
  
  {...more computation...}
  
  return(ans)
}

Here, the function f1 calls f2 within its body. Suppose f2 takes more than few seconds to run (e.g.: Simulations, Model building, etc). Let’s assume that the f2 function already has separate tests written to test its validity. As f2 function’s validity is ensured, and since it takes a lot of time to finish, it may be better to skip the interim_val <- f2(...) statement in tests for the f1 function. Also, a general expectation from a suite of tests is that they should finish within few minutes (if not seconds). In such a case, using a stub of f2 while testing f1 is desirable.

API

Check if the stub is called with specified arguments

stub$expects(...)

Stub will check the incoming arguments for the specified set of arguments. Throws an error if there is a mismatch.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$expects(a = 2)

stub_of_sum$f(2)
#> NULL
stub_of_sum$f(3)
#> Error in stub_of_sum$f(3): Following arguments are not matching: {'a'}
#> Argument: 'a':
#> 1/1 mismatches
#> [1] 2 - 3 == -1

stub$strictlyExpects(...)

The set of specified arguments should be exactly matched with the set of incoming arguments.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$strictlyExpects(a = 2)
stub_of_sum$f(2)
#> Error in stub_of_sum$f(2): Function was called with the following extra arguments: 'b'

The above call resulted in the error because the incoming set of arguments was a = 2, b = 1, but the defined set of expected arguments consisted only a = 2.

stub_of_sum$strictlyExpects(a = 2, b = 1)
stub_of_sum$f(2)
#> NULL

stub$onCall(#)$expects(...)

The stub expects the specifed arguments on the nth call.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$onCall(3)$expects(a = 2)

stub_of_sum$f(100)
#> NULL
stub_of_sum$f(100)
#> NULL
stub_of_sum$f(100)
#> Error in stub_of_sum$f(100): Following arguments are not matching: {'a'}
#> Argument: 'a':
#> 1/1 mismatches
#> [1] 2 - 100 == -98

stub$onCall(#)$strictlyExpects(...)

The stub expects the exact set of specifed arguments on the nth call.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$onCall(3)$strictlyExpects(a = 2, b = 2)

stub_of_sum$f(2)
#> NULL
stub_of_sum$f(2)
#> NULL
stub_of_sum$f(2)
#> Error in stub_of_sum$f(2): Following arguments are not matching: {'b'}
#> Argument: 'b':
#> 1/1 mismatches
#> [1] 2 - 1 == 1

Make the stub return a specified value

stub$returns(...)

Unless otherwise specified, the stub always returns the specified value.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$returns(0)

stub_of_sum$f(2)
#> [1] 0

stub$onCall(#)$returns(...)

The stub returns the specified value on the nth call.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$onCall(2)$returns(0)

stub_of_sum$f(2)
#> NULL
stub_of_sum$f(2)
#> [1] 0

stub$withArgs(...)$returns(...)

The stub returns the specified value when it is called with the specified arguments.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$withArgs(a = 2)$returns(0)

stub_of_sum$f(1)
#> NULL
stub_of_sum$f(2)
#> [1] 0

stub$withExactArgs(...)$returns(...)

The stub returns the specified value when it is called with the exact set of specified arguments.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$withExactArgs(a = 2)$returns(0) # won't work because value for b is not defined
stub_of_sum$withExactArgs(a = 2, b = 1)$returns(1)

stub_of_sum$f(1)
#> NULL
stub_of_sum$f(2)
#> [1] 1

Make the stub throw an error with a specified message

stub$throws('')

Unless otherwise specified, the stub throws an error with the specified message.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$throws('some err msg')

stub_of_sum$f(2)
#> Error in output_func(do_this$behavior, do_this$return_val): some err msg

stub$onCall(#)$throws('')

The stub throws an error on the nth call.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$onCall(2)$throws('some err msg')

stub_of_sum$f(0)
#> NULL
stub_of_sum$f(0)
#> Error in output_func(do_this$behavior, do_this$return_val): some err msg

stub$withArgs(...)$throws('')

The stub throws an error when it is called with the specified arguments.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$withArgs(a = 2)$throws('some err msg')

stub_of_sum$f(1)
#> NULL
stub_of_sum$f(2)
#> Error in output_func(do_this$behavior, do_this$return_val): some err msg

stub$withExactArgs(...)$throws('')

The stub returns the specified value when it is called with the exact set of specified arguments.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$withExactArgs(a = 2)$throws('good') # won't work because value for b is not defined
stub_of_sum$withExactArgs(a = 2, b = 1)$throws('nice')

stub_of_sum$f(1)
#> NULL
stub_of_sum$f(2)
#> Error in output_func(do_this$behavior, do_this$return_val): nice

Get the number of times the stub has been called

stub$calledTimes()

Using this, one can obtain the number of times, the stub has been called.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

ans <- stub_of_sum$f(3)
ans <- stub_of_sum$f(3)
stub_of_sum$calledTimes()
#> [1] 2
ans <- stub_of_sum$f(3)
stub_of_sum$calledTimes()
#> [1] 3

Extra

Convenience functions to reduce repetition of code.

stub$onCall(#)$expects(...)$returns(...)

On nth call, the stub will check for the specified arguments, and if satisfied, returns the specified value.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$onCall(1)$expects(a = 1)$returns('good')
stub_of_sum$onCall(3)$expects(a = 3)$returns('nice')

stub_of_sum$f(3)
#> Error in stub_of_sum$f(3): Following arguments are not matching: {'a'}
#> Argument: 'a':
#> 1/1 mismatches
#> [1] 1 - 3 == -2
stub_of_sum$f(3)
#> NULL
stub_of_sum$f(3)
#> [1] "nice"

This is same as calling stub$onCall(#)$expects(...) and stub$onCall(#)$returns(...) separately.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$onCall(1)$expects(a = 1)
stub_of_sum$onCall(1)$returns('good')
stub_of_sum$onCall(3)$returns('nice')
stub_of_sum$onCall(3)$expects(a = 3)

stub_of_sum$f(3)
#> Error in stub_of_sum$f(3): Following arguments are not matching: {'a'}
#> Argument: 'a':
#> 1/1 mismatches
#> [1] 1 - 3 == -2
stub_of_sum$f(3)
#> NULL
stub_of_sum$f(3)
#> [1] "nice"

stub$onCall(#)$strictlyExpects(...)$returns(...)

On nth call, the stub will check for the exact set of specified arguments, and if satisfied, returns the specified value.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$onCall(1)$strictlyExpects(a = 3)$returns('good')
stub_of_sum$onCall(3)$strictlyExpects(a = 3, b = 1)$returns('nice')

stub_of_sum$f(3)
#> Error in stub_of_sum$f(3): Function was called with the following extra arguments: 'b'
stub_of_sum$f(3)
#> NULL
stub_of_sum$f(3)
#> [1] "nice"

stub$onCall(#)$expects(...)$throws('')

On nth call, the stub will check for the specified arguments, and if satisfied, throws an error with the specified message.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$onCall(1)$expects(a = 1)$throws('good')
stub_of_sum$onCall(3)$expects(a = 3)$throws('nice')

stub_of_sum$f(3)
#> Error in stub_of_sum$f(3): Following arguments are not matching: {'a'}
#> Argument: 'a':
#> 1/1 mismatches
#> [1] 1 - 3 == -2
stub_of_sum$f(3)
#> NULL
stub_of_sum$f(3)
#> Error in output_func(do_this$behavior, do_this$return_val): nice

stub$onCall(#)$strictlyExpects(...)$throws('')

On nth call, the stub will check for the exact set of specified arguments, and if satisfied, throws an error with the specified message.

sum <- function(a, b = 1) return(a + b)
stub_of_sum <- stub(sum)

stub_of_sum$onCall(1)$strictlyExpects(a = 3)$throws('good')
stub_of_sum$onCall(3)$strictlyExpects(a = 3, b = 1)$throws('nice')

stub_of_sum$f(3)
#> Error in stub_of_sum$f(3): Function was called with the following extra arguments: 'b'
stub_of_sum$f(3)
#> NULL
stub_of_sum$f(3)
#> Error in output_func(do_this$behavior, do_this$return_val): nice

A note regarding with_mock

testthat::with_mock function is going to be deprecated in a future release of testthat. mockr library’s with_mock function is meant to be the replacement for testthat::with_mock. Slight changes will be needed while replacing testthat::with_mock with mockr::with_mock. Refer to mockr’s README for more details.

Also, it is no longer possible to mock functions from external packages. If you are doing this, either change the code to avoid such a case or use a wrapper function similar to the url_downloader <- function(url) GET(url) example in this document. To know more about the reasons behind these changes, refer to the following github issues : with_mock interacts badly with the JIT, Prevent with_mock from touching base R packages.

License

Released under MIT License.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].