Building Better Blazor Unit Tests - Lessons From Automating WPF Tests
October 23, 2023
• 758 views
I've been writing tests for all sorts of different C# applications for many years. These include console applications, services, WPF apps, and even old-school WinForms applications! And when it comes to tests, we have all sorts of different flavors including unit tests, functional tests, and even things like UI automation tests. Here's an example of an approach I like from my WPF days applied to Blazor unit testing.
Have you subscribed to my weekly newsletter yet? A 5-minute read every weekend,...
View Transcript
writing tests for things with user interfaces can be pretty challenging because there's so many different ways to do it one of the most obvious things that we consider is that we have something that can actually navigate through the application clicking on things entering text and stuff like that unfortunately these types of tests can be really brittle so if you're doing a lot of iteration on your user interface it feels like there's a ton of work to keep your test up to dat with your user interface and you're almost doubling the amount of work that you have to do not to mention a lot of the automation that does a lot of the clicking and navigation and stuff like that requires that there's rendering taking place and there's different timing constraints and sometimes they're just flaky now these types of tests are more like a functional
test or an integration test and the mindset behind them is that you're kind of going through a test scenario or a test case acting like a user interacting with the app now for this video I want to focus on Blazer and I want to talk about unit testing instead of functional or integration test in the previous video that I made on this which I'll link right up here I focused on an approach using a framework called B unit that allowed us to modify the state of our Blazer page but it did require that we had to go change a private member to something public so that it was accessible to the framework I mentioned in that video and I'll repeat it here that that's not really my preferred way to do things because it feels like we have to change the nature of the thing
that we're working with just to make it testable and while you could argue that a lot of the things that I propose about writing testable code are aligned with that to me it feels like we're trying to expose something that shouldn't have to be exposed in the general sense so in this video I want to show you a different approach that I've used successfully with WPF development zaml and everything before the Blazer age and this is built on some experience doing UI Automation and other types of unit testing and trying to bring it all together so that developers can have a lot of confidence in the code that they're creating that has a user interface without ever actually having to look at the UI itself now before I jump over to visual studio to walk through how we're going to refactor some of this code
I just wanted to mention that if you haven't subscribed to My Weekly Newsletter I'll put a link in the comments below and you can check that out it's totally free and I send it out once a week to have focus on net topics and software engineering topics to help Kickstart your learning for the weekend I appreciate the support so with that said let's jump over to visual studio and check out this code so here in Visual Studio on my screen I have the counter razor page from the first example that we looked at and this is actually a slightly modified version of the counter razor page that we get when you create a Blazer application a new blazer project inside a visual studio it's just part of that template but we did have to make a slight modification which we can see covered on lines
16 through 19 here to be able to test it with B unit and that modification is that we had to change this private field which I have commented out here so int current count had to be changed into a public property with a getter and a Setter as as well as this parameter attribute now as I mentioned in the intro to this video this allows it to work with this testing framework called B unit but I don't really like doing this because I feel like we're fundamentally changing what this class is doing in essence we're exposing this because we want to be able to test with it and sure it makes it testable but it kind of feels like we're cheating just to make it testable and I think that there's better ways to do this where we don't have to muddy the waters and
expose something that probably should be private and not accessible to anyone just so that we can test now someone could make the argument that we could have made this internal perhaps and then that way we could expose our internal members to a test project or something like that and that way just something that we trust like a test project could go ahead and operate on this but again I think we can structure some of this code to be a little bit more elegant and give us some control when we're testing and also just when we're designing code in general so what we're going to be doing in this video today is refactoring some of this code to go look at a different variation of how we can structure some of the code behind and I'm borrowing from my experience as I mentioned earlier with WPF
and zaml and we're going to be looking at some type of view model options that we can consider here I'm not going to be going super in depth about controllers and Views and view models and all these different things with binding but what I am going to show is that we can pull out some of this code into something that would be like a view model or a controller or some type of view model controller hybrid however you want to look at it and really just pull some of the core code out of here even though it's a really small amount of code and put it into something that allows us to test just a little bit easier so have a look at what's on the screen here with this current count and this increment count so basically everything that's within this code block and
I'm going to show you this counter viw model that I created which should represent it very similarly now this view model class that I've created here just has this public property like we had in the code we were just looking at it does have a private seter on it though in my opinion the way that this original code was designed in the Blazer template application is that this current count is not supposed to be modifiable directly by anyone else at least not through the property or the field that they had declared and that's why it was marked as private so in the spirit of that this view model will also just have a publicly readable current count proper what's different though is that we're going to be exposing this public method called increment count which will do that increment for us and that means that
someone on the outside of this current view model class is not able to directly assign the current count because there is no public Setter but they are able to increment it as many times as they want and to be a little bit pedantic for how I usually like to structure things this is combining two different things kind of like having some type of control so something like a controller as well as some State and I usually like to have uh a view model or something like this strictly be a data transfer object a dto and usually I would not have a method like this on it it would just be a set of properties but in this example because it's super simple I just wanted to show you these things combined there's really no strict rule here we're just going to use it as an
example to walk through with B unit so that we can look at a different way of modifying the state another thing to call out that makes this example feel a little bit like Overkill is that I have an interface on this class and you're looking at this and I can totally see the look on your face going Nick holy crap there's no reason you need to have an interface on something so simple like this that's totally Overkill and look I agree with you like I said if I was making this a view model that was strictly a dto there's no way that I would be putting an interface on this as well but when we go to walk through the test example and that code that we'll be looking at I just wanted to be able to illustrate how we can mock things so I'm
just combining a couple of different things like interfaces mocking and view models all into one video so that you have some tools to work with on your own and it will be a little bit more clear when we start to look at the test code but just to have a little bit of a spoiler we're not going to be testing anything with this increment count method at all what we are going to be doing is creating a mock of this view model and because we're using a mock and using the interface to create that mock what we're going to be able to do is set up the mock to have a current count on it directly and never have to worry about calling this at all and I'll pause here for Just A Moment of clarity because if that was a little bit confusing the
point that I want to make in this video and the last video is that we're talking about unit tests and when I talk about unit tests I'm doing my best to kind of isolate the things that we're testing and that means that if we're looking at things like calling a method to increment something or in this case for Blazer we want to be able to press a button to get the increment to me that's starting to combine a couple of different things it's starts to feel more like a functional test and that's totally cool those are really valuable tests that we can and probably should add in our application for coverage and that's because they simulate a lot of real user activity and kind of how things are going to work and integrate together so those are good but if we're talking about unit tests
and why we might want to write a unit test that's to give us really isolated coverage on particular behavior and different aspects of how things work within a class that are very specific so in the last video the test case that we were looking at related to the count here was that the label that shows the count and the value would be updated to show the current value the correct value once we update the state of the component and to have more of a unit test kind of feel I was skipping over trying to press the button to actually have it increment because in a more complicated system something a lot more complicated than pressing a button having a count go up you might be triggering a bunch of other stuff and having other side effects that cause the desired Behavior to show up in
your assertions but if we just wanted to look at again like a unit testing kind of focus that that label is reflecting the state properly and that's it nothing to do with all these other things that might be going on behind the scenes then being able to update the state with a mock and just being able to render that component to get the right output means that it's a lot more isolated if you're hearing this and going that seems like Overkill I'd rather just do the functional test and get that out of the way totally get it those are probably way quicker to write and give you really good coverage but a unit test like this is a lot more Surgical and can give you some confidence over more complicated areas where you really want to prove that particular things are working as you expect
it's not that one's right and one's wrong they're just different things we are talking about unit tests I just wanted to clear that up and let's go back to the code and check out how we can start to use mocks with this VI model and clean up some of our Blazer unit testing okay so hopefully after that explanation you've calmed down a little bit from seeing this interface if we jump over to what it looks like yes it's just as simple as you might have imagined it has the current count which is a get only and then this increment count method which would be used to increment this current count property I already mentioned that when it comes to writing these tests we're not going to be focused on calling this method we're simply going to be mocking this counter view model interface and setting
up this current count property with the value that we expect one more step before we jump over to the test code for all of this is looking at how this razor page had to change I created this counter 2 razor page just so that I could keep the original counter razor page in my application at the same time for comparison purposes but with that said we can look at how we're using this view model in this counter 2 razor page the whole goal of this razor page is that it should be very similar to the original one and just demonstrate the difference for how this refactor looks the first thing that call out is right at the top here I am going to be using this at inject syntax here to pass in an eye counter view model and we're going to be calling it
counterview model and that's going to require that we need a change to our dependency injection to pass in an instance of this eye counter view model lines four and six aren't super important here I just added this two in here for differentiating purposes but the other parts that are a little bit more interesting come in when we're using the view model itself so one important note here is that instead of having the count as a field which used to be somewhere in this code block we're able to just use the counter view model and ask for its current count the button itself didn't change I've just updated the increment count method down here to call the counter viiew model increment count of course as I'm making this video I'm realizing that I could have simplified this a little bit more we can totally ditch this
code block Al together and instead for this onclick we can put the counter view model directly here and voila no more code behind in this file so there it is all on one screen we've just replaced some of the code with the counter viw model got rid of the code behind and now we're in a position where we can go test this keeping in mind that we are going to have to do some type of dependency injection to get this counter view model passed in jumping over to the counter page two tests let's walk through how we can use xunit Mock and B unit all together to write some unit tests for this page I know some folks aren't happy with using theq mock framework and that's totally cool you can use whatever you like it should be absolutely applicable for this example so do
as you will I still use mock personally just because I'm comfortable with it in the last video if you've gone ahead and watch that you'll recall that I used test context up here in the class death definition itself and that was because we were inheriting from the test context in those examples I did mention in that video that we don't have to inherit from that if we don't want to I was just doing it for demonstration purposes so in this video we're looking at an alternate way to do that where we can create a new test context instance and I'm doing that in the Constructor here and that means we no longer have to inherit from test context as a base class but we will make this class disposable so that we can go ahead and dispose of this test context when we're done jumping
back up here I'm creating a new mock repository so that is specific to the mock mq framework and from there I'm creating a new instance of our counter view model as a mock which is using the mock repository as we can see right here now on the test context that comes from B unit we can access the collection of services and this is going to be what allows us to alter the dependency injection to pass in this counter view model mock that we've just just created so everything that you see on my screen right now when we go to run xunit facts and these are the individual test that we have for xunit each time we go to run one of those The Constructor is going to get called then we're going to execute the fact then we're going to come and call dispose so
that's going to be the life cycle and I just wanted to explain that because if you're not totally familiar with xunit you might be asking how the heck is this ever going to work if we have more than one test what's going on here but that's because the life cycle suggests that this has to get called then the actual test method and then the dispose method right after now that's going to be all of our setup let's look at some of the individual tests that we have the first test that I have on screen is similar to what I had in the previous video where we were just looking at the header of the control or the page that we were looking at the core of the test is really trivial and it seems a little bit silly but I wanted to demonstrate it again
here because some things change based on how we have stuff set up if we're just looking at lines 42 through 45 all that we're doing is looking for the H1 tag and then ensuring that it says counter two pretty simple right by calling render component on the test context with our page what we're able to do is get that new instance created have all the dependency injection magic work for us so it's going to pass in that mock that we have then once it renders that component we can go look up that markup and ensure that it matches so this part is pretty simple and straightforward what's going on with the mock you might be looking at this part and saying well why did you have to go set this up if we're just looking for the H1 tag and it's a good question because
even when I wrote this test the first time it failed because I didn't have this but then I realized in order for B unit to go properly render that component it's going to require that that label has the text generated for it and in order to format that text it has to be able to pull a count off of that view model so yes even though in this particular case we're only interested in pulling the H1 tag and ensuring that's valid in this type of setup we do have to ensure that we have the base State setup which requires that our view model is also configured to be able to pull these property values and look I know I know it feels like Overkill I introduced an interface we have mocks it's just like a dto why the heck am I doing this I apologize
it's a bit of an exaggerated example we definitely don't need to have a mock with mock setups on something like a dto cuz as you're reading this test you're going that's way too much work just to set up a dto I know but I wanted to be able to show you how this works with MOX but you can totally replace the mock setup and the interface part and just create a new instance of that view model that's set up how you'd like pass that in through the dependency injection and it will work as you expect I'm showing you this because in unit tests not functional test we want to be able to isolate stuff to particular scenarios just our system under tests which is our component in this case so again take a breather it's okay interfaces and mocks seem a little bit Overkill here
but this is the reason why just showing you the tools that you have to use so that you can apply them as you need in your own code so for this example we've probably wrapped it all up totally acknowledging that having this mock set up with something that looks just like a dto certainly feels like Overkill but hopefully you understand why I've included that but this wasn't really the interesting example when we think about what was covered in the last video the interesting example is when we wanted to change the state of the count and just because I know some of you haven't gone back to watch that video but you totally should I want to show you what that looked like and why I wanted to change how this was all looking so earlier in this video I showed you that we needed to
make something public in a property and add an attribute on it and that's because this setup code right here in B unit required that we did that in order for us to be able to change the the state of our component we had to go modify how that looked and felt just to be able to do this part and while this is convenient and it's cool to be able to use I felt like it was making it exposed for other people to be able to access the state and manipulate it even outside of a testing context and I don't mean like malicious Bad actors I mean like other developers or even yourself accidentally modifying something because you had access to it so this is the part that I was trying to work around let's jump back over to the current task that we're walking through
and how this changes based on our new view model setup just to be extra fancy I've switched over to an xunit Theory instead of a fact so we can play with a couple of different variables when we're setting this test up but let's walk through how this looks the first part is back to this mock that we're setting up and I'm going to be setting up the Mock's current value with the counter value that we're passing in as part of this Theory so that means we'll have three variations of This Test passing in one three and five and if you don't want to use Mock and you didn't want to have an interface and all that set up this is where you would create a new instance of the view model and be able to pass that in through dependency injection so that when you
call render component with your page or your component that new instance of the view model with the state you want will be passed in automatically for you but before render component is called we need to have all of our state configured as we expect for our test and that means after it's called we have a render component that should look just like we want based on the state so I've created this expected markup which has the current count here and the value passed in using string interpolation it's just the same parameter that's passed in right up here from the theory and that means that we can call render component looking for the P tag and ensuring that that markup matches exactly what we have here including this formatted text with a counter value so The View U model in essence allowed us to skip over
the b-unit framework calls to be able to directly set the state on the component itself and instead we've moved that state over to something like a view model some type of dto to hold the state for us separate from the component we modify that and pass it in with dependency injection so that we get that isolated from The View and that's going to wrap up the example for today where we're changing how we're testing blazer from modifying some State and having going to expose some private Fields as properties over to something that I'm more familiar with that looks like views and view models what I found personally is that the more that we put into views directly into the markup and things like that the more challenging it is to write specific targeted tests on those views the way that I end up approaching this
then is to split out the pieces from the views themselves and create things like view models controllers or whatever other names you want to assign to things where're pulling State and logic out from The View itself now from your perspective this might feel like Overkill depending on your application the types of tests and stuff you want to write but when I personally like to write really surgical tests on different areas of a user interface I like having this flexibility because I can go write the tests on the controller part I can write the tests on The View model part which is probably a detail and doesn't really warrant test but then I can also write test on The View itself when you jam all of the stuff into the view and it has logic State and presentation types of pieces to it it makes it
really challenging because it's all coupled together and that might be totally cool if you're just writing functional tests and you can use B unit to do that and in fact I'm probably going to have to make some followup videos showing how we can click stuff doing functional tests instead of doing this type of view model thing where we're setting the state because those types of tests are valuable too so I hope that you found this useful I wanted to demonstrate to you that we have different different ways of doing things and if you want to write really surgical unit tests and change the state directly on things this is an approach that you can take remember there's pros and cons to everything so it's not like one thing is right and one thing is wrong it's really about what you're trying to accomplish so thank
you so much for watching if I do have another follow-up video using B unit to do more functional tests I'll put it right here and you can watch that next thanks and we'll see you next time
Frequently Asked Questions
What are the challenges of writing tests for user interfaces?
Writing tests for user interfaces can be quite challenging due to the many ways to interact with the application, such as clicking and entering text. These tests can become brittle, especially if you're frequently iterating on your UI, leading to a lot of extra work to keep your tests up to date. Additionally, automation for these interactions often requires rendering and can be flaky due to timing constraints.
Why do you prefer not to expose private members for testing purposes?
I prefer not to expose private members just to make them testable because it feels like we're fundamentally changing the nature of the code. Exposing these members can lead to unintended consequences, where other developers or even myself might accidentally modify something that should remain private. I believe there are better ways to structure code to maintain encapsulation while still allowing for effective testing.
What is the benefit of using a view model in Blazor unit testing?
Using a view model in Blazor unit testing allows me to separate the state and logic from the UI components. This separation makes it easier to write isolated unit tests, as I can focus on testing the behavior of the view model without being concerned about the UI. It also helps to avoid exposing private fields and keeps the code cleaner and more maintainable.
These FAQs were generated by AI from the video transcript.