Unit Testing the Authentication Service (with NUnit and Moq) - FULL STACK WPF (.NET CORE) MVVM #13

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everybody welcome back to simpletrader last time we set up our authentication service that users can log in and register with simpletrader and after doing that we came into this startup method and we just created an authentication service and tried logging in and tested out to make sure that worked now in this episode we're going to be taking this a step further and actually writing unit tests for our authentication service so what we're going to do is get rid of this little test right here in our startup and we're going to create a new project so let's go to our solution add a new project and we're going to be doing an n unit test project so we're going to be using end unit for our unit tests and let's go ahead and select that dot net core click next for the project name we're going to be using simple trader because that's the name of our application we're going to add dot domain because we're going to be testing our domain layer with this project this test project is not going to be testing the nad framework the wpf it is only going to be testing the domain layer and then finally we will add dot tests because these are tests for our domain layer let's go ahead and create that project and there we go we have our unit testing project let's go ahead and get rid of this first unit test and let's open up our domain project so what i like to do is create a new folder and i'm going to name this services and what i'm trying to do is make my folder structure in my test project match the folder structure in my actual domain project so we have services and services here let's open that up and we have another folder for authentication services so let's create a folder here in our test project and now inside this folder we have an authentication service so down here we're going to create our authentication service test so as you see our folder structure is parallel with the folder structure in our domain layer so it's easy to find the tests that correspond to the class that is being tests now we're in our test class right here let's go ahead and make this public and the first thing we need to do is mark this class as a test fixture so that end unit recognizes this class contains unit tests and actually runs the test inside the class next thing we're going to do is actually create a test so we're going to create a public void so your test methods actually have to be void they should not return anything and let's go into our authentication service and let's figure out what we want to test so what i want to do is i want to test this login method so let's go back to our test and we're going to start our function name with login and now back in our authentication service what scenario do i want to test i want to test the user logging in with an account that actually exists with the correct password so let's go into our test and after this login after we prefix this function with login we're going to put an underscore and then describe the scenario so we are logging in with the correct password for an ex for existing user and that will put another underscore and now let's go back to our authentication service what do we want that scenario to return well we wanted to return an account with the correct username so after that last underscore we will end this method with returns account for correct username and actually back in this scenario let's make this for existing username and that is our naming convention here so we have the method we're testing the scenario and what we expect to return of course this is a really long method name so maybe you could cut it down a little bit with correct password for existing username i don't know it's not a big deal because this is just a test project so the names can get a little bit long now what we need to do is actually mark this method as a test so that end unit will actually run this method when we go to test and now let's actually implement this test so as you all probably know the three a's of unit testing so we need to arrange then we need to act and then we need to assert and make sure that we get the correct result so first thing we need to do is actually arrange and inside here we are going to create everything that we need to run our test scenario so obviously we're going to need our authentication service so let's go ahead and actually add a reference to our domain project we haven't done that yet so add that reference that we can use our authentication service and we'll just call this authentication service and just create one right here now as you see our authentication service actually needs an i account service and an ipassword hasher and we're not actually going to be passing in actual implementations of our account service or the ipassword hasher because if we passed in say our entity framework account service right here then if there was a bug in that entity framework account service it would make it look like there was a bug in our authentication service and another thing with that is we don't want to be testing against our database that would be crazy but what we're going to do instead is actually create mocks for these services for this password hasher and then we're really only testing our authentication service because that's what this is all about this is our authentication service test so what we need to do is create mocks for these dependencies right here and to do that we're going to go to our unit test project and we're going to manage our new get packages and we're going to browse for mock and install that and this package is going to let us mock those dependencies as we will see in just a second so back in our test project what we're going to do is create a mock for our i account service so this is like a generic class right here this mock take in whatever you want to mock we'll call this the mock account service and just create it right here and let's import everything that we need so we need our domain service for that i account service and we need to import mock as well and now we have this mock account service now in our authentication service the first parameter here we can specify that as our mock account service dot object and dot object will give us the actual implementation of an i account service or not really the actual implementation but the actual mocked implementation if that makes any sense now we're also going to be mocking a password hasher as well so let's create that mock right here let's import password hasher we'll call this mock password hasher and there we go now we can take this mock password hasher and pass that through the constructor as well dot objects so now we have our authentication service let's act on that service so let's use their authentication service and we're testing the login method here and we're testing with the correct password for an existing username so we don't actually have this let's add those in our range method or in our range section up here so we will create a string for the username and we'll just call this test user and then we'll have a string for our password as well and we'll just call this test password you can really just call it whatever you want so now back in this login method let's specify that username and the password and as we see this returns an account so let's grab that so we can make sure we get the correct result have to import account as well and as we see we get a little bit of error here because this is an async method so we need to await it and if we want to await it then this test needs to be an async task so i did previously mention that your test shouldn't return anything this should just be voids but end unit does support an async task because you're really just returning a task that gets awaited and then nothing really gets returned so now we get our account back and what we're trying to do is make sure the account that gets returned is for this user so in this assert we will assert dot r equal and in here we can pass in the expected and the actual so our actual username let's actually grab that right here so actual username is equal to the account that we got back dot username or dot account holder dot username and up here let's change this username to expected username and now in this r equal method this assertion right here we can pass in the expected username and we will compare it to the actual username and make sure they are equal and now let's actually run this test so to run your test let's go up to this test tab and we're going to open the test explorer and as you see down here we have our test let's go ahead and run it and just see what happens and obviously our test fails now actually i want to debug this test let's go to our authentication service and put a break point right here and now what i'm going to do is right click this and click debug so we can actually walk through the test so we hit this breakpoint we have our account service we're going to call get by username for our test user so let's step over that and we look at storage account you return null and that is because we didn't set up our mock to return anything for get by username so let's go back to our test right here and we need to take our mock account service and we need to set up and we need to set up the get by username method to actually return an account so what we're going to do is set up it's just a inline expression lambda right here we're setting up get by username and if the string passed in is the expected username then it's going to return async since this is an async method and what it's going to return is a new account and this account is going to have an account holder which is a new user and that user is going to have a username with our expected username so now let's go back here and let's put a breakpoint again and let's debug this test so now we get by username for our test user and now it returns that stored account we look at the account order and they have a username of test user so now let's step over this verify hash password and the password result is failed and the reason it is failed is because we didn't set up our mock to return success so we need to set up our password hasher to return success when the correct password is used so let's go down here and we're going to use our mock password hasher set up again and we're going to set up the verify hash password and this actually takes two arguments here the hash password and the provided password now in our case all that we have is the provided password so we're gonna pass that in and what we're gonna do for this first argument is we're gonna say it dot is any string so basically verify hash password for this hash password part it can take in any string we don't care what the user's hash password is we are just going to return success if this password gets passed in because we're assuming that for the scenario the user has the correct password so this is going to return password was it hash was it password verification result dot success and there we go so now let's actually run this test and there we go our test passes so there's our first unit test let's write another one and to do that i'm actually going to copy all of this and paste it down here and let's actually change this to be login with incorrect password and what we're going to do for that is it's going to throw an invalid password exception so throws invalid password exception for username and obviously we need to refactor because we have a lot of duplication here so what we can do with end unit is we can create a method and we'll mark it as a setup method and it'll just be a public void setup and what's going to happen is for every test this method is going to run so not just once per like all of your tests for each individual test this setup method is going to run so what we're going to do here is do a little bit of our arranging in this setup method and what we can do is create our mox and create our authentication service that we test against so let's go ahead and grab this so we create our authentication service and then we create our mocks as well in the setup method let's grab those and there we go now what we're going to do is actually put these mocks and this authentication service that we create into fields so that our tests can access them so let's create a private field up here for a mock i password hasher and this will just be a mock password hasher and then we'll set that field in the setup method and we'll do that for all of our stuff so let's create a mock for our i account service and we'll call this mock account service and set that down here and then last but not least we'll do that for our authentication service as well and now we need to update this to use the private field and there we go we've set up our mocks and our authentication service and we can use that in our test now so now this is going to use that private field and we do our setup right here and we can get rid of that down here so our mox we create them down here don't need to do that anymore and now we still have some duplication obviously since we did copy and paste this but this login with incorrect password the mock setups are actually going to be different so verify hash password is going to return failed now because the user has the incorrect password and let's set these to the private fields real quick and get by username in this case is still going to return the expected user so now let's do some acting down here so what we're going to do is we're going to make sure that this login method throws an invalid password exception so our act and our assert here are kind of mixed together because what we need to do is call assert.throws async since it's an async method and now inside here we specify the method that is going to throw and that's just going to be through a lambda so authenticationservice.login is going to throw and what we wanted to throw we can specify in this generic throws async we want it to throw a invalid password exception so let's paste that in there import that and now the rose async returns that exception so let's get that it's an invalid password exception and now so we're going to make sure it throws and now inside this assert we need to make sure that the exception that it through is for the expected username so what we can do is say actual username equals exception dot username and then we just run our assertion again for our equal now another thing to note is that since we do throws a sink right here we don't actually specify any async await send it so this can just be a regular void method and now let's go ahead and run our test again and there we go our test pass it did throw the invalid password exception now if we look at our test so far we see that both of these scenarios are for an existing username so the user does exist now let's write a test for if the user does not exist let's copy this test that we have so far and let's change this to with non-existing username so that means this get by username method is going to return it's actually going to return non but you can't set up a method to return all so what you can do instead is just remove the setup completely and since you didn't set up that method on the mock it's just going to return all anyways so now let's actually see what would happen if our get by username returns no well we don't actually have to walk through this and do some debugging we can see stuart account would be gnaw and then we would try to get the password hash on the stored account and we would get a naught pointer so what we need to do is if stored account is null we need to throw an exception because that means the user doesn't even exist and you can't log in anyways so we're going to do a little if statement right here so if stored account equals null we are going to throw a new exception and let's actually create an exception up here and we will call this user not found exception and we'll make that public and inherit from exceptions that we can throw it and we'll generate all of these constructors get rid of the protected ones we don't need it in this case and what we're actually going to do is put a property up here for the username that we could not find and then let's select that and let's add it to these constructors like that and there we go okay so now back in our login method down here we are going to throw a new user not found exception and it's going to take in the username so now that we have this case defined we need to run through the scenario in our test so we already have this set up and what we're going to do is when we run this method we expect to get back a user not found exception let's grab that same thing as before it has a username property on it and we're just going to make sure that there is the exception and that the username on the exception matches the username that we pass in so let's go ahead and run this test and there we go our test passes so we have fully tested this login method we've gone down each code path and i feel pretty confident about this login method next up we're going to test our register method and as you can see we have a lot of scenarios to test for so let's head back into our unit tests and let's create a new test so this is going to be a public async task and this time we are testing register and the first scenario that we are going to test is registering with passwords that do not match so with passwords not matching and that's going to return passwords do not match which is the enum right here so passwords do not match so first thing we need to do is arrange so what we are going to do here is specify what we expect to get back from our method so it's going to be a registration result and that's going to be the expected and we expect to get back passwords do not match now we need to act so we need to actually use our authentication service and that's going to give us back a registration result and it's going to be the actual result and we're going to call our authentication service.register and we need to pass in an email a username a password and a confirmed password now for this method we don't really care about the username or the email we only care about the password and the confirmed password so what we can do is specify an it dot is any here because we simply do not care it can be any string and we can do that for the username as well and then for the password we can we should actually create variables for this so we can specify a password up here like that and then our confirm password obviously is going to not match this actual password right here so we'll just call this confirm test password and then we can pass those in here like that and now we can do our assertion and we need to make sure that the actual result was our expected passwords do not match like that so let's go ahead and run this test and there we go our test passes so now let's see what our next scenario is so now we need to see if get by email returns or if get by email does not return law then the email already exists so let's go ahead and copy this just to set up a little bit so with already existing email returns email already exists so that's going to be our registration result that we expect email already exists and let's get rid of this and this time we need to make sure the passwords do match so we can just say test and test so our passwords will match and we'll get past this block right here and now we need email account to return a non-null value so if email account is not null then the email already exists so we can set that up down here so our mock account service we're going to set up get by email and let's actually create a variable up here for that email so we'll just call this test gmail.com and we'll pass that into this so if get by email receives this email then we're gonna return async a new account so the account that comes back will not be gnaw so that means we're going to get email already exist so now let's actually pass this email into our register method like this and now we're ready to run this test and there we go our test passes so now we need to pretty much do the same thing but instead get by username is going to return a non-null value let's go ahead and grab this we're just going to copy this method and so this is going to be with already existing username is going to return username already exists so now this time get by email is actually going to return gnaw so that means we don't need to set it up because we wanted to return nas that we get past this block and then we fail on this one and said the result to username already exists so what we can do is just change this to get by username like that change this string to username and we'll just make this test user move that down here and get by username is going to return the not null value but since we didn't set up git by email it's going to return null so get by email returns a null we get past there get by username does not return law username already exists so now for this register method we can make this the it is any string because we don't care what the email is and register it does need to be the username that we expect so that git by username returns the new account and this time the registration result is going to be username already exists so now let's run this test and there we go that test passes so what is next so we've done all of our validation and now the result is going to be success so now we pretty much just need to do the mighty test where we get the success result so let's copy this and register so we're going to be registering with new user or with non-existing user and matching passwords and it's going to return success there we go so now we can remove this setup right here forget by username because we just want git by username to return null now and we need our passwords to match we have that down there our expected result is success we can remove username because we don't really care what the username is just set that as a string and now we can run this test and this is the simplest test ever so we're just going to run through that method register and the test does pass and one last thing i actually want to try is making this making these passwords and the confirm password let's actually try and make those it dot is any string as well and see if that is still considered matching because i just don't like having these hard-coded strings right there i assume i could move them the variables but let's just try and do this instead so let's run that and yeah the passwords do match so we can make all these tests it dot is any strings so we can get rid of those and get rid of those i just don't like having those strings hard coded there i feel like it's a little bit distracting like someone might look at that string and be like is that meaningful for the test and i really just want it to be clear that for these methods we're not really doing anything with those passwords and those confirmed passwords so that pretty much sums up all of our unit tests let's get one big test run let's run all of our tests make sure they all pass and there we go our authentication service is tested so that's going to wrap up this episode thank you guys for watching i hope you guys learned a little bit about unit testing it's super powerful helps you deploy with confidence as they say and i am feeling pretty confident about this authentication service if you have any questions criticisms or comments be sure to leave them below in the comment section just clean this up a little bit but other than that if you enjoyed the video be sure to leave a like or subscribe for more
Info
Channel: SingletonSean
Views: 4,353
Rating: undefined out of 5
Keywords: wpf, mvvm, easy, hard, difficult, switch, views, microsoft, visual, studio, binding, viewmodel, property, class, interface, user, control, window, learn, how to, architecture, pattern, new, switching, toggle, button, content, main, programming, tutorial, full, stack, entity, framework, model, access, object, core, .net, c#, service, layer, project, trade, development, refactor, clean, update, delete, delegate, abstract, data, login, register, authentication, password, hash, database, migration, username, unit, testing, moq, mocking, nunit, test, integration
Id: NnET1OHAeDk
Channel Id: undefined
Length: 31min 47sec (1907 seconds)
Published: Sat Apr 18 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.