Note: Slides do not tell the whole story of the talk, so take the stand alone slides with a grain of salt. Things may be taken out of context.
Note: Slides do not tell the whole story of the talk, so take the stand alone slides with a grain of salt. Things may be taken out of context.
Having issues testing Formik with react-testing-library? Validation errors not showing up? Formik validation happens asynchronously, so you need to use react-testing-library’s findBy*
methods and await
for the validation to finish (which can be done by waiting for some UI to show up). Scroll to the very bottom to see a working test.
Formik is a library for building forms in React. It abstracts away some of the annoying part of forms like validation, errors, fields, etc. so that you don’t have to. It can also do super useful things like keep track of if a field has been touched by the user or not yet, so you can delay showing validation errors until they actually interact with the field.
react-testing-library is… well… exactly what it sounds like – a library for testing React. The library forces you to write tests that follow it’s primary principle:
The more your tests resemble the way your software is used, the more confidence they can give you.
In my opinion, this is one of the single most important aspects of writing good tests. Tests that do too much mocking or too much “make sure this function is called with this data” is usually not a very useful test in my eyes.
Besides, giving you confidence the software is working – one of the biggest values of automated tests is giving you the confidence to to be able to refactor the internals of your code later on. However, if the test knows too much about the internals, then you usually have to refactor both the code and the test, which can be really problematic, in my experience.
Ok – soapbox over.
Let’s say I have an InputField
component. This component will have 3 goals to accomplish:
That way all of our labels, inputs, and validation errors are consistent across our entire application.
import React from "react"; | |
import { Field } from "formik"; | |
const InputField = props => { | |
return ( | |
<Field | |
name={props.fieldName} | |
render={({ field, form }) => ( | |
<div> | |
<label htmlFor={props.fieldName}>{props.labelName}</label> | |
<input {…field} id={props.fieldName} type="text" /> | |
{form.errors[props.fieldName] && form.touched[props.fieldName] ? ( | |
<div data-testid={`errors-${props.fieldName}`}> | |
{form.errors[props.fieldName]} | |
</div> | |
) : null} | |
</div> | |
)} | |
/> | |
); | |
}; | |
export default InputField; |
Note: I’m omitting a few things you may want in real life (like PropTypes, placeholders, making the input type a prop, styling validation errors, etc.) for brevity since it’s not super important for this blog post.
Alright – let’s go to test the InputField component (normally we’d write the test first if we were doing TDD, so this is a little backwards, but bear with me).
One of the tests we need to write is to make sure the validation errors show up correctly. The flow of that test will be like this:
In order to achieve this, you may start out with a test like this:
import React from "react"; | |
import { render, fireEvent } from "react-testing-library"; | |
import InputField from "./InputField"; | |
import { Formik } from "formik"; | |
test("should have validation error given input field is touched and error exists on form", () => { | |
const fieldName = "firstName"; | |
const labelName = "First Name"; | |
const { getByLabelText, getByTestId } = render( | |
<Formik | |
validate={values => { | |
let errors = {}; | |
if (!values.firstName) { | |
errors.firstName = "Required."; | |
} | |
return errors; | |
}} | |
> | |
<InputField fieldName={fieldName} labelName={labelName} /> | |
</Formik> | |
); | |
const input = getByLabelText(labelName); | |
// Call blur without inputting anything which should trigger a validation error | |
fireEvent.blur(input); | |
const validationErrors = getByTestId(`errors-${fieldName}`); | |
expect(validationErrors.innerHTML).toBe("Required."); | |
}); |
However this test will fail, because it says that the div with our validation errors is not rendered (notice, it should be below the input tag, but it’s not):
So what’s the problem? Well – Formik validation happens asynchronously, so while on line 28 above we call blur
without entering any data in order to trigger a validation error. On line 30 we immediately go looking for that validation error. The problem is – Formik validation hasn’t finished executing when we go look for the UI to change, so when we go to expect
our validation error UI is there, it’s not because the validation is still executing.
So the solution then is to make the call to find the UI asynchronously via findByTestId
instead of getByTestId
. All of the findBy*
functions in react-testing-library are asynchronous and react-testing-library will wait up to 4.5 seconds for the UI to appear before failing the test, which should give Formik enough time to run validation in this case.
So the successful test can look something like this:
import React from "react"; | |
import { render, fireEvent } from "react-testing-library"; | |
import InputField from "./InputField"; | |
import { Formik } from "formik"; | |
test("should have validation error given input field is touched and error exists on form", async () => { | |
const fieldName = "firstName"; | |
const labelName = "First Name"; | |
const { getByLabelText, findByTestId } = render( | |
<Formik | |
validate={values => { | |
let errors = {}; | |
if (!values.firstName) { | |
errors.firstName = "Required."; | |
} | |
return errors; | |
}} | |
> | |
<InputField fieldName={fieldName} labelName={labelName} /> | |
</Formik> | |
); | |
const input = getByLabelText(labelName); | |
// Call blur without inputting anything which should trigger a validation error | |
fireEvent.blur(input); | |
const validationErrors = await findByTestId(`errors-${fieldName}`); | |
expect(validationErrors.innerHTML).toBe("Required."); | |
}); |
I changed line 6 to make the test async
, line 9 to destructure findByTestId
and line 30 to call await findByTestId
instead. Now the test passes:
Hope this helps!
Note: Slides do not tell the whole story of the talk, so take the stand alone slides with a grain of salt. Things may be taken out of context.
Code: Blazor ToDoMVC and Blazor on Electron
Note: Slides do not tell the whole story of the talk, so take the stand alone slides with a grain of salt. Things may be taken out of context.
Code: Blazor ToDoMVC and Blazor on Electron
Note: Slides do not tell the whole story of the talk, so take the stand alone slides with a grain of salt. Things may be taken out of context.
Code: https://github.com/scottsauber/twin-cities-code-camp-security-headers
Updated 12/1/2019 to work with Blazor 3.0+
Use bind-value:event="oninput"
instead of bind
in order to get real feedback as you type. bind
only databinds during the onchange
event which requires losing focus on the input whereas bind-value:event="oninput"
databinds on every keystroke. Note you will also have to add a bind-value="PropertyNameHere"
as well. See line 3 below:
@page "/" | |
<input @bind-value="SearchTerm" @bind-value:event="oninput" /> | |
<span class="text-muted ml-5"> | |
Showing @FilteredToDos.Count out of @ToDoItems.Count | |
</span> | |
<h4 class="mt-4">To Do's</h4> | |
<ul> | |
@foreach (var toDo in FilteredToDos) | |
{ | |
<li>@toDo.Name</li> | |
} | |
</ul> | |
@code { | |
// Initialize SearchTerm to "" to prevent null's | |
string SearchTerm { get; set; } = ""; | |
// Imagine this was retrieved from an API, just hardcoding for demo purposes | |
List<ToDoItem> ToDoItems => new List<ToDoItem> | |
{ | |
new ToDoItem { Name = "Garbage" }, | |
new ToDoItem { Name = "Dishes" }, | |
new ToDoItem { Name = "Wash clothes" }, | |
new ToDoItem { Name = "Water flowers" } | |
}; | |
List<ToDoItem> FilteredToDos => ToDoItems.Where(i => i.Name.ToLower().Contains(SearchTerm.ToLower())).ToList(); | |
} |
Blazor is an experimental SPA framework, built by Microsoft, running on top of WebAssembly that lets you write C# that runs client-side in a browser. This allows you to share logic between your server-side C# code and your client-side C# code (such as models, validation, etc.). Blazor limits (if not entirely eliminates) your need to write JavaScript to make a SPA. You can learn more by visiting https://blazor.net/.
I want to build a Search feature that allows the user to search an unordered list (<ul>
), purely client-side. I don’t want to go back to the server to search, because in my scenario my data results are fairly small, don’t update often, and I want the response time to the user to be fast and avoid a round-trip. I was looking around and didn’t see much on the Interwebs showcasing this, which is why I wrote this post.
Note: This scenario could also be tweaked to search through a table (when pulling in a grid component is overkill), searching FAQs in an accordion, or really anything that needs a client-side search, it doesn’t have to be a list. The same concept applies.
Let’s build a simple component that loops over a list of ToDoItems and displays them in a list. You could imagine this could be used for a ToDo app, but I’m going to remove all the typical ToDo functionality (like checking off tasks as completed) since it will just add noise to this example.
It’ll look like this on the front-end:
And the component that powers it looks like this:
@page "/" | |
<input bind="@SearchTerm" /> | |
<span class="text-muted ml-5"> | |
Showing @FilteredToDos.Count out of @ToDoItems.Count | |
</span> | |
<h4 class="mt-4">To Do's</h4> | |
<ul> | |
@foreach (var toDo in FilteredToDos) | |
{ | |
<li>@toDo.Name</li> | |
} | |
</ul> | |
@functions { | |
// Initialize SearchTerm to "" to prevent null's | |
string SearchTerm { get; set; } = ""; | |
// Imagine this was retrieved from an API, just hardcoding for demo purposes | |
List<ToDoItem> ToDoItems => new List<ToDoItem> | |
{ | |
new ToDoItem { Name = "Garbage" }, | |
new ToDoItem { Name = "Dishes" }, | |
new ToDoItem { Name = "Wash clothes" }, | |
new ToDoItem { Name = "Water flowers" } | |
}; | |
List<ToDoItem> FilteredToDos => ToDoItems.Where(i => i.Name.ToLower().Contains(SearchTerm.ToLower())).ToList(); | |
} |
On line 3 we are taking an input
and using the bind
attribute to databind the input’s value to the SearchTerm
property. That SearchTerm
property is then used on line 30 to search through our ToDoItems
and return any matches in the FilteredToDos
property. Our list loops over the FilteredToDos
and displays them out. Let’s take a look how this works in real life with this gif:
By default, Blazor’s default bind
fires on the onchange
event. The onchange
event doesn’t fire until the input element loses focus. This means when a user types in the Search input they need to lose focus on the input before the Search takes place. While this is okay UX-wise, it’s not exactly ideal. My ideal world would be to have it fire while the user is typing so they can get real-time feedback without having to do any extra work (like clicking/tabbing out of the input or clicking a search button).
A little known feature about Blazor’s data binding is that bind
has options that let you bind to different events, not just onchange
. For our scenario, we want to databind to the oninput
event which fires immediately every time the value has changed. We can do this by swapping out bind="@SearchTerm"
for bind-value="SearchTerm" bind-value:event="oninput"
on line 3 here:
@page "/" | |
<input @bind-value="SearchTerm" @bind-value:event="oninput" /> | |
<span class="text-muted ml-5"> | |
Showing @FilteredToDos.Count out of @ToDoItems.Count | |
</span> | |
<h4 class="mt-4">To Do's</h4> | |
<ul> | |
@foreach (var toDo in FilteredToDos) | |
{ | |
<li>@toDo.Name</li> | |
} | |
</ul> | |
@code { | |
// Initialize SearchTerm to "" to prevent null's | |
string SearchTerm { get; set; } = ""; | |
// Imagine this was retrieved from an API, just hardcoding for demo purposes | |
List<ToDoItem> ToDoItems => new List<ToDoItem> | |
{ | |
new ToDoItem { Name = "Garbage" }, | |
new ToDoItem { Name = "Dishes" }, | |
new ToDoItem { Name = "Wash clothes" }, | |
new ToDoItem { Name = "Water flowers" } | |
}; | |
List<ToDoItem> FilteredToDos => ToDoItems.Where(i => i.Name.ToLower().Contains(SearchTerm.ToLower())).ToList(); | |
} |
Now when we run our app, we get a much better UX:
Hope this helps someone else in a similar scenario!
If you have a scenario where you have multiple file types (.pdf, .docx, etc.) stored somewhere (in a database, file system, etc.), that need to be downloaded, you can automatically figure out the Content Type by newing up a FileExtensionContentTypeProvider
and call TryGetContentType
to get the Content Type and pass that to the File
result helper. See lines 8-16 below
[HttpPost("Download/{id:guid}")] | |
[ValidateAntiForgeryToken] | |
public async Task<IActionResult> Download(Guid id) | |
{ | |
// Retrieve some file from some service | |
ApplicationFile file = await _fileService.GetFileAsync(id); | |
var fileProvider = new FileExtensionContentTypeProvider(); | |
// Figures out what the content type should be based on the file name. | |
if (!fileProvider.TryGetContentType(file.NameWithExtension, out string contentType)) | |
{ | |
throw new ArgumentOutOfRangeException($"Unable to find Content Type for file name {file.NameWithExtension}."); | |
} | |
// contentType will be application/pdf for .pdf's, application/vnd.openxmlformats-officedocument.presentationml.presentation for .pptx's, etc. | |
return File(file.Contents, contentType); | |
} |
A Content Type is how the server tells the browser what type of file the resource being served is. That way the browser knows how to render whether it’s HTML, CSS, JSON, PDF, etc. The way the server does this is by passing a Content-Type HTTP Header. This value of the header will look like “text/html” for HTML, “text/css” for CSS, “application/json” for JSON, “application/pdf” for PDF’s, and so on. A complete list can be found on the IANA official docs.
Note: A Content Type can also be called a MIME type, but because the header is called Content-Type, and ASP.NET Core calls it the Content Type in the code I’m going to be showing, I’m going to call it Content Type for consistency throughout this post.
The good news is, for a vast majority of the static files you’re going to serve, the Static Files Middleware will set the Content Type for you. For scenarios where you need to set the Content Type yourself, you can use the FileContentResult
in your Controllers
or PageModels
via the File
helper method used on line 11 below.
[Route("File")] | |
public class FileController : Controller | |
{ | |
[HttpPost("Download/{id:guid}")] | |
[ValidateAntiForgeryToken] | |
public async Task<IActionResult> Download(Guid id) | |
{ | |
// Grab a test.pdf back one directory. Look ma – it even runs on Linux with Path.DirectorySeparatorChar! | |
var fileContent = await File.ReadAllBytesAsync($"..{Path.DirectorySeparatorChar}test.pdf"); | |
return File(fileContent, "application/pdf") | |
} | |
} |
If you set the wrong Content Type, then you may cause issues for your application. For example, the PDF rendered in the code above will render a PDF in the browser like this:
But what happens if I replace that “application/pdf” string with “application/json” to try to tell the browser the PDF is really JSON? Well… let’s find out:
Well that’s not good. So, setting the correct Content Type is pretty important. (Also, yes I know I need to update Chrome…. don’t judge me. I have a bunch of tabs open in another window that I’m totally going to look at some day, ok?)
Let’s say you have a scenario where you allow admin users to upload files that allows some customer users to download those files. Those admin users can uploads all sorts of file extensions such as a pdf, pptx, docx, xlsx, etc. that customers can then download themselves. This means that you can’t assume and be sure what the Content Type should be, so you need to inspect the file extension to figure it out. No big deal, there are a few ways to solve this, but the simplest is to just write a trusty ol’ switch statement like lines 11-25 below to handle every file type we allow.
[HttpPost("Download/{id:guid}")] | |
[ValidateAntiForgeryToken] | |
public async Task<IActionResult> Download(Guid id) | |
{ | |
// Retrieve some file from some service | |
ApplicationFile file = await _fileService.GetFileAsync(id); | |
string extension = Path.GetExtension(file.NameWithExtension); | |
string contentType = ""; | |
switch (extension) | |
{ | |
case ".pdf": | |
contentType = "application/pdf"; | |
break; | |
case ".xslx": | |
contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; | |
break; | |
case ".docx": | |
contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; | |
break; | |
// and so on | |
default: | |
throw new ArgumentOutOfRangeException($"Unable to find Content Type for file name {file.NameWithExtension}."); | |
} | |
return File(file.Contents, contentType); | |
} |
The problem with that is trying to maintain a list of all those mappings yourself is annoying, and likely leads you to adding “just one more” when your users want to support another file type you didn’t previously have. It’s also prone to typo’s, because some of these content types are ridiculously convoluted.
Luckily, ASP.NET Core already has maintained this list for us via the FileExtensionContentTypeProvider. So all you have to do is new it up, and call TryGetContentType which acts like a lot of the TryX-out pattern you see sprinkled throughout .NET. It returns a bool and an out variable with the content type. Usage looks like lines 8-16 below:
[HttpPost("Download/{id:guid}")] | |
[ValidateAntiForgeryToken] | |
public async Task<IActionResult> Download(Guid id) | |
{ | |
// Retrieve some file from some service | |
ApplicationFile file = await _fileService.GetFileAsync(id); | |
var fileProvider = new FileExtensionContentTypeProvider(); | |
// Figures out what the content type should be based on the file name. | |
if (!fileProvider.TryGetContentType(file.NameWithExtension, out string contentType)) | |
{ | |
throw new ArgumentOutOfRangeException($"Unable to find Content Type for file name {file.NameWithExtension}."); | |
} | |
// contentType will be application/pdf for .pdf's, application/vnd.openxmlformats-officedocument.presentationml.presentation for .pptx's, etc. | |
return File(file.Contents, contentType); | |
} |
So there you have it, now you don’t have to maintain some crazy list of Content Type mappings, you can just lean on ASP.NET Core which maintains those mappings for you.
Hope this helps.