
Getting Started with {shinytest2} Part I || Example + basics || RStudio
00:00 Introduction 00:48 Overview of the demo Shiny app 03:00 Running record_test() 04:44 Results from record_test() 07:18 A note on .png files created during testing 08:52 Debugging with shinytest2 09:32 Using app$view() to open a visual representation of a headless browser Part 2 - Exporting values: https://youtu.be/7KLv6HdIxvU Part 3 - Using shiny.testmode: https://youtu.be/xDxa_mDwN04 Manually testing Shiny applications is often laborious, inconsistent, and doesn’t scale well. Whether you are developing new features, fixing bug(s), or simply upgrading dependencies on a serious app where mistakes have real consequences, it is critical to know when regressions are introduced. shinytest2 provides a streamlined toolkit for unit testing Shiny applications and seamlessly integrates with the popular testthat framework for unit testing R code. shinytest2 uses chromote to render applications in a headless Chrome browser. chromote allows for a live preview, better debugging tools, and/or simply using modern JavaScript/CSS. By simply recording your actions as code and extending them to test the more particular aspects of your application, it will result in fewer bugs and more confidence in future Shiny application development. Read up on shinytest2 here: https://rstudio.github.io/shinytest2/ Learn more about Shiny here: https://shiny.rstudio.com/ Got questions? The RStudio Community site is a great place to get assistance: https://community.rstudio.com/ Content: Barret Schloerke (@schloerke) Motion design and editing: Jesse Mostipak (@kierisi) Theme song: Brad PKL by Blue Dot Sessions (https://app.sessions.blue/browse/track/113507)
image: thumbnail.jpg
Transcript#
This transcript was generated automatically and may contain errors.
Shinytest2 is a wonderful package to help do regression testing for your Shiny applications. And we're going to perform this regression testing in inside testthat. And it's not regression testing like linear model or something like that. This is consistent behavior over time. And so we do not want it to regress or we do not want it to change. That's what I mean for regression testing.
Overview of the demo Shiny app
So, shinytest2. Before we get into shinytest2 and how it works, I just kind of want to do this demo app. This demo app is, you know, short little toy example. We have a fluid page that has a text input. It's like, what's your name? You know, we can fill it out. And then we also have an action button of greet and text greet. And then we have two text outputs. One is a greeting, like, hello, Barrett. And then the other one is what is the first letter in your name?
And this is neat. Like, it's a short little app. So, if I have it on here, I have Barrett. I click greet, we get hello, Barrett. And if I'm here, we could do, like, shinytest2. What is your name? Shinytest2. The first letter is S. So, it works. It's a nice little app. The app for the server, what it does is we have a rendered text that requires the name. And given a greet click, we will say hello, name. And then for the first letter calculation, we require a name and we extract the first character of the name. And we send it to lower. And we will bind all of that calculations to when the greet button is clicked. Given this first letter, whenever it updates, we will then call rendered text and say the first letter in your name is this first letter.
Awesome. So, let's imagine that I have well, I have now created this app or I am working on a team and I want to make sure that, you know, behaviors are consistent and, like, tests are worth so much because you don't necessarily need to communicate that because the test is there. And if the test breaks, then we can figure out what's happening. And this also allows you to do other things like sweeping code changes. Like, what if I want to change the guts of how first letter is calculated? I know this is a small function, but, you know, it could be much more complicated. And having tests allow you to do these sweeping changes with confidence. Otherwise, you just kind of look at it and go, nah, it's close enough. I don't know. I hope I covered everything. At least with tests, we can say all of the tests pass. And if you're missing some, we can add them in and make your test more robust.
And having tests allow you to do these sweeping changes with confidence. Otherwise, you just kind of look at it and go, nah, it's close enough.
Running record_test()
When you run record test, it opens up your application in the Chrome browser or Chromium based browser. Mine by default is set to Chrome. It'll have your app in this iframe. And on the sidebar is actually the recorder. And we can see there's some buttons to call expect shiny values or expect screenshot. And then there's also an area of all the code that will be executed later to that is able to replay your recording. So, let's make a recording. This is what is your name? My name is Barrett. So, let's greet Barrett and we'll say hello, Barrett. The first letter in your name is B. And we can see that I set inputs name is Barrett. And then I clicked the greet button. And then an output value was updated.
Awesome. So, I can test this or save this as hello, Barrett. Because that is the name that I used. And just for my reference, and I'll click oh, but I need to make sure first before because I can't save right away. Won't let me. So, I need to say expect shiny values. And this will keep track. The expect values will keep track of all your input, output, and exported values. And exported values we'll get to in another video. So, once we have at least one expectation, we can then click save test and exit. This will save the test and then immediately run the test, play back the test again. And there are some warnings because we have some new snapshots, but that's okay. And it looks like it all passed because there's also nothing to say that the snapshots would fail.
Results from record_test()
So, let's take a look at what was created. So, we have our app.r here. Awesome. And then the tests folder will be a sibling to that. And in there, we will have a test that.r, which internally it just has shiny test 2 test app. And then next to the test that.r is a test that folder. There's three parts here. The first part is the snaps folder. We'll go inside that in a second. The second one is the setup.r. Setup.r is there for you when you're a little more complicated apps where you have an.r folder or you have global.r for your Shiny application. We load all of those information inside setup.r so that your tests have that information available. And the last one is test shiny test 2. This is the default file name for recordings and it'll say shiny test 2 recording whatever name you gave.
So, if we remember from the recording that we made, I created a new app. It gives the same height and width of the recording window. That way when it replays, it's under the same height and width. We then say app set inputs. I typed in Barrett for the name. I then clicked the greet button and then we called app expect values. Expect values is actually a wrapper around Shiny test or actually a wrapper around test that expect snapshot file. And this file will contain a JSON representation of your input, output, and export values. And that is stored in the snaps folder. So, we will open up snaps. We'll open up shiny test 2 because that is the name of the file test shiny test 2. So, that name. And then there was the first JSON file that was created.
This one, if we click it, has my input, output, and export. So, my inputs is at the time of calling expect values, which was after setting an input and after clicking the greet button. We have input of greet 1 for one time to click the button and name was Barrett. The output was first letter was the first letter in your name is B and the greeting is hello Barrett. We didn't export any test values yet, but we'll get into that in a later video. So, this is our test. This is awesome. We can, if we want, change the height and width. We can add more tests. This is our test file to work with.
A note on .png files created during testing
And as a side note, you might notice that there's a PNG file here created next to the JSON file. This is there because images typically are fairly brittle and we don't like to use them as if we can help it because there's lots of things outside of our control that we can't, you know, account for such as, you know, if the R version updates or let's say you change your version of DT or let's say you update your system font or you, yeah, like there's many different things that are outside of shiny, outside of R that are not in your control and screenshots will fail if this happens. And that's, you know, no one likes to see that, but that'll happen.
So if you can try to do everything as much as possible using your JSON or your values and try to just test the things that you own, unless you're a package developer, then, you know, by all means, we need to start taking pictures and making sure things render properly, but otherwise we can give that responsibility back to the package developers, not us as app developers. But the image is there to take us a snapshot to let us know what the app looked like when we called expect values. So let's do hello Barrett, and this will open up my application and we can see that chromote believes the app looked like this, where it says what is your name? Barrett, greet, hello Barrett, your first letter name is B. Awesome, that worked out great.
Debugging with shinytest2
So one of the things that we can do interactively is we can take our app and we can run it in the console, as long as we're sitting in our working directory of our Shiny application. This uses the AppDriver object, and this object is something that drives shinytest2, and it runs your Shiny application in the background, and it also does your communication with chromote. And so it handles all of that, and we can set our inputs, we can also retrieve inputs and outputs or different values, and click, you know, many different things.
So if we do this, one of the things that I really enjoy is that we can call AppDollar view, and view will open up a web page of our headless Chrome browser. And I think this is really, really neat, because now it's not a black box as to what is happening. So now I have my app, and this is what chromote is representing as what is happening within the app. So if we actually just step through this code one at a time, we can actually see the app update to the left. So we can call setInputs, name is Barrett, click greet. And then if I wanted to, I could call expectValues, or I could even say AppDollar getValues, getGetValues, and this will return all the values. Or if I wanted to do something like just a particular one, I could say AppGetValues with output equals greeting, and it's just that one particular value. Or since if it's just your one value, you can say getValue, and it'll turn back the regular result.
And I think this is really, really neat, because now it's not a black box as to what is happening.
So it's very exciting. Like you can debug your headless Shiny application, and headless Chrome instance, and your Shiny application all at the same time, save the codes that you want, and then we can replay them afterwards by just going in the top right and saying run tests. And the IDE will load up. It'll get there, and eventually pass one for that expectValues, because that's the only expectation in this whole test file. Test complete. Perfect.


