in this video we're going to talk all about unit testing in java do you ever write a complicated piece of code and you think you've got it all coded correctly but you're just not 100 percent sure you can use unit tests to prove that your code is doing exactly what it's supposed to do we'll go over exactly what unit testing is how it works and how you can implement them in your own java programs i also have a full java course available in a link down in the description so go check it out first just what exactly is unit testing unit testing is a type of software testing where one individual piece of code or unit is being tested by itself so a unit test isolates one single piece of code and verifies that that piece is working correctly usually for java that single piece of code is going to be a class or even a method inside a class so unit tests are just code that you write also in java that hit that individual method that you want to test and verify that it's doing exactly what it should do we're going to be using the junit test framework to write our unit tests all you have to do in order to use junit is to add it as a dependency in whichever dependency management tool you're using so here i'm using maven so here i'm pulling in the dependency for the most recent version of junit as of this recording if you're using gradle instead it's the same kind of thing we're going to start with a super simple method to write unit tests for so here i have a class called simple calculator and all it has is a single method called add that just takes in two ins number a and number b and adds them together and returns the sum first how do we even go about creating a unit test if you're using intellij there's a shortcut all you have to do is have your cursor somewhere inside the class that you want to create a test for and hit ctrl shift t that will give you a little pop-up and you can just click create new test it's automatically using a certain pattern for the test class names that it creates since this will be testing the simple calculator class it names the test file simple calculator test and that's totally fine that works for us so let's go ahead and click ok what that will do is automatically create a test class for you you can see at the top here that it automatically imported everything in this assertions package and we'll talk more about that later also it's important to note exactly where it created this test file so if you go over into your project view we can see that our simple calculator class is under source main java and the unit test file that it created is under source test java and that's exactly the convention that you'll see any test that you create is going to be in the exact same package structure as the file that you're testing only it'll be under source test instead of source main okay so let's go and write our first unit test in junit a unit test is just a method that you label with the at test annotation so just type in at test and this is the dependency you want org.junit.jupiter.api.test so now we have to decide what exactly we want our first unit test to be in general you want to make it so the unit tests that you write are only testing one thing one single scenario the method that we want to test is just adding two numbers together so maybe a simple test can just be verifying that two plus two results in four so let's go over here and write our method to do that as long as you're using junit five your test methods don't need to be public and they don't need to return anything so your return type is just going to be void next you want to come up with the name of your test and it can be whatever you want it's just the name of your test method but it should be something that's very descriptive of the exact scenario being tested here and what the result should be here we're just testing that two plus two should equal four so let's make that our method name a two plus two should equal four this method won't take any parameters so we can just have an open and close parentheses here inside this test method we want to call this simple calculator's add method so first let's go ahead and create a simple calculator object that we can actually call this method on so simple calculator calculator equals new simple calculator as a quick tip ever since version 10 java has what's called local variable type inference which basically means that when you're creating a local variable like this instead of using the entire class name here java can infer what the type of this variable is supposed to be so you can actually just put var here so now we want to call the add method on this calculator object so calculator dot add and we want to test adding two plus two so we'll send in two and two this add method is going to return a result and here we want to verify that it's returning four so how do we do that in junit and in pretty much every unit testing framework when you want to do that type of verification you use what's called an assert statement so here we want to verify we want to assert that the result of this method call equals four to do that we can use a method built into junit called assert equals and that method gets two parameters passed into it the first parameter is the value that you expect the result should be so we expect that two plus two should equal four the second parameter that you pass into assert equals is the actual result of your test so the actual result of our test will be whatever calculator dot add returns when we send in two plus two so let's just close our parentheses and add a semicolon so we're saying that the result of calling this add method with two and two should equal four and if it doesn't our test will fail so now we have our first unit test how do we run it and make sure that it passes in intellij there's a couple of ways probably the simplest is next to each test method you'll see this icon here in the margin this allows you to run that particular test and when you click it it'll give you a couple of options this first option will just run your test so let's go ahead and click it and see what we get all right so it's telling us all of our tests passed so this test seems to be working great but what would we have seen if this test failed so what if our code was doing the wrong thing and instead of adding these two numbers it was subtracting them so now because the code isn't doing the right thing our test should fail so let's go ahead and run it again okay so now we see that our test failed and if we click the result of that test it shows us exactly the line in which that failure happened which is simple calculator test line 10 right here on our assert equal statement and it also tells us exactly what the problem with the assertion was it's telling us that the expected result was 4 but the actual result it received was 0. so this assert equals statement fails failing our test and in this case that's good our test should be failing if our code is doing the wrong thing so if we change this back to addition and re-run our test we should see it pass again and we do everything past everything's green all good virtually all the unit tests that you write are going to have one or more assert statements and there are a lot of different types of them that assert different things assert equals is the one we've already used that's probably one you're going to be using pretty often however if you want you can also call assert not equals so with assert not equals your test will only pass if the two parameters that you pass into that method don't equal each other and if they do equal each other your test will fail you also have assert true and that will just take in as a parameter some kind of expression that evaluates to a boolean true or false if that expression evaluates to true your test passes and if it evaluates to false your test fails you can also use assert false which will do the opposite it'll pass the test when the expression that you send in evaluates to false you also have assert null which will only pass the test if the expression that you pass in is null and you have assert not null which will only pass the test if the expression that you pass in is not null now you might be thinking well there's a whole bunch of different ways we could use these assert methods to prove that this method is doing the right thing right so here we just happen to be using assert equals but we could just as easily have said uh you know assert true and then we could pass into this assert true method the result of calling this method with two and two double equals four so using assert true like this is totally valid and will work fine we can see that everything is still passing there are potentially some problems with just having one single unit test scenario even for a method as simple as this one for example if we go back into our simple calculator and mess up our code in some way let's say instead of doing addition we accidentally did multiplication so now we're returning number a times number b well if we go back to our test and rerun it our code is now bad it's doing the wrong thing it's not actually adding both numbers but of course this test still passes because 2 times 2 does equal 4. one of the goals of the unit tests that we write should be that whenever the code is not doing the right thing at least one of our unit tests should fail right now our code is bad it's doing multiplication instead of addition but we don't have any unit tests that are failing our unit tests are saying everything's great so that doesn't mean that this unit test is bad this is a valid scenario but it does mean that it's probably a good idea to add more scenarios to our unit test suite so what you can do is just copy that test method that we made and change it to verify another test scenario so let's say it was something like 3 plus 7 should equal 10. we'll send in the values 3 and 7 and the value that we expect when we call this method with 3 and 7 is of course 10. now we could just run this new single test that we just wrote but we can also just have it run all the tests in this class by going up to the class declaration in the margin here and clicking this to run all tests all right so it's telling us one test failed and one test passed of course as we saw before our two plus two should equal four test is still passing but now we have a failing test as well which we should because our code's bad it's telling us hey we expected this value of 10 but i got the value 21. so you hop over to this add method in your calculator class and see ah some doofus uh changed this to multiplication instead of addition let's go ahead and click this first one to rerun all of our tests and make sure our code is good and they all pass and they do we're looking good now let's try and create some unit tests for a little bit more complicated of a method this method is called determine letter grade and all it does is take in the number grade you know the percentage generally zero to a hundred and then it returns the letter grade as a character that matches that number first anything less than zero is negative and just isn't really a valid number grade so we throw an illegal argument exception in that case if it's less than 60 we return an f for the grade otherwise if it's less than 70 we return d less than 80 we return c less than 90 we return b otherwise we know it's greater than or equal to 90 so we just return a first we need to go ahead and create our unit test class so we'll hit ctrl shift t as the shortcut and it's creating a class called greater test and let's hit ok in this method we have a bunch of pretty clear separate scenarios that will need to be tested each separate scenario that we come up with should be tested in their own test method you should never combine multiple scenarios into one single test method so for example one good scenario that we might want to test is that if we send in a 59 as the number grade we should get an f as the letter grade so let's go ahead and create that let's create a new test method that we label with at test here we're just verifying that 59 should return f so let's just call our test method 59 should return f first let's create our greater object so we can call that determine letter grade method on it equals new greater so similar to before we want to assert that when we call greater dot determine letter grade and pass in the value 59 that we get a result of f let's go ahead and run our test and make sure it passes and it does great you may have noticed that over here in addition to just running our test normally we also have an option to run our test with coverage when we run our test with coverage intellij will show us exactly which lines of code were executed when our tests were run so let's run this same test but with coverage okay so our tests run they pass as normal but then it also pops up this chart with coverage information it's telling us that we're only hitting 33 percent of the execution lines in that class 4 out of 12 lines and if we look over here at the actual class in the editor here you'll see different colors green or red depending on whether the unit test that we just ran hit that particular line or not one of our goals for the unit test that we write for this method should be that we eventually get to hitting 100 of the lines inside of it let's go ahead and do that let's write more tests to cover each of these other scenarios so maybe another good test would be that a 69 should return a letter grade of d 69 should return d run greater test with coverage okay it's complete our tests pass now let's look at the coverage over in greater test and it shows that yes we are now hitting lines 10 and 11 because they're showing as green so let's go ahead and do the same thing for c b and a 17 9 should return c c 79 89 should return b b 89 99 should return a a 99 run greater test again with coverage and it's looking good all of these situations f d c b and a are all green now because we're hitting all of those scenarios the only line left where it's still showing red is line five here where when we send in a negative number it throws an exception we'll talk more about how to handle this exception scenario in just a moment first we want to make sure that our tests are handling all of the possible edge cases in all of these simple scenarios let's say i went over into our determine letter grade method and change the criteria for getting a letter grade of c to being instead of less than 80 i changed it to less than 81. right now if you pass in a number grade of 80 it will hit this condition and return the value of c when an 80 should be a b instead so we have a situation where our code is wrong but if we go back and run all of our unit tests they all pass and say that everything is okay again this doesn't necessarily mean that our test scenarios are bad it just probably means that we need more of them so for example we should have a test scenario that validates that edge case where if we pass in an 80 it returns a b as it should so let's go ahead and create that test case so 80 should return b so if we send in a letter grade of 80 it should return b let's rerun all of our tests and now we do get one failing test and this test failure is good our code is currently wrong it's not doing what it should do and so we should have at least one failing test and now we have a test scenario to cover that edge case that an 80 should return a b so now if we go back and fix our code to change this back to an 80 and re-run our tests and they all pass but now with that added scenario our unit tests are that much stronger and more strictly guarantee that our code is doing exactly what it should so let's go back into our tests and add some other edge cases a 90 should return a a 90 70 should return c c 70 60 should return d d 60 finally zero should return f f and zero okay so they all pass but now our unit tests are even stronger than they were before so we can go in and mess around with any one of these numbers and just change it by one like we can change this 90 to a 91 and run all our unit tests again and at least one of them is going to fail so here we go and 90 should return a is failing because now a 90 is returning b so that's really great we have a really strict set of unit tests that are guaranteeing the proper functionality of our code okay so let's get back to this scenario here where if you send in a negative number it throws an illegal argument exception so in our unit test we do want to have a scenario where if we send a negative number we verify that it's throwing an exception as it should but how do we do that so first let's hop back over to our test class and just try it out so let's go ahead and copy this test method and we can say negative one should return illegal argument exception and let's just verify what happens if we call this determine letter grade method with a negative number and right now the test fails under normal circumstances if the code that you hit throws an exception it will automatically fail your unit test instead there's a certain assert statement that you can use in junit 5 that asserts that an exception was thrown and that assert is called assert throws now this assert throw method also takes a couple of parameters but it works a little bit differently the first parameter that it takes is the name of the class of the exception that you expect to be thrown so here we expect an illegal argument exception to be thrown so our first parameter is going to be illegal argument exception dot class the second argument that it takes is actually the executable piece of code that you need to call to get that exception to be thrown you can do that by using what's called a lambda now lambdas themselves are a whole other topic for an entire other video so i'll just show you what you need to know to get them to work for this particular use case so to pass in your lambda as your second parameter you would first just do an open and close parenthesis then space hyphen and greater than so it kind of acts like an arrow and then you want to open a curly braces and then inside those curly braces is where you want to put the code that executes your test so in our case the code that we want to run that will make this exception get thrown is calling this determine letter grade method with a negative one as the parameter so we'll just paste that on in here and put our semicolon here after the closed parenthesis so what this assert throws is doing is it's verifying that when this piece of code is executed it throws an illegal argument exception so let's go ahead and run our test and make sure it passes and it does if calling this piece of code wouldn't have thrown an illegal argument exception then our test would have failed so for example we could go back into our grader class here so what if it didn't throw an exception and instead returned a letter grade of f so when we run our test we would expect this to fail and that's exactly what it does it says we expected an illegal argument exception to be thrown but nothing was thrown so that leads us to the reasons why it's a good idea to write unit tests for your code first and foremost is the obvious reason it ensures that your code is actually correct you don't have to be pretty sure that your code is correct you can write a bunch of test scenarios to be absolutely sure that your code is correct and i even often find myself making changes and improvements to code as i'm thinking up new unit test scenarios so it makes my code better when i write unit tests once you have a comprehensive suite of unit tests in place that guarantee that your code is working the way that it should you can refactor that code any way you want with absolute confidence because you know that if you refactor in such a way that you mess up the code a little bit one of your tests is going to fail and tell you that there's something that you need to fix so for example in this class we now have a comprehensive set of unit tests that covers all of our edge cases technically we don't need the elses in all of these else ifs so i can go in and delete them all get rid of that one that one so now we've made some changes into this class that might scare you if you don't have a complete set of unit tests ready to hit your code really hard and make sure that it's doing what it should so we can go back to our grader test and run all of them again and here they go they all pass and so we know that even with the changes that we made our code is still doing exactly the right thing the subject of unit testing is a really really deep one and we've just scratched the surface here as always if you enjoyed this video or learned something please let me know by leaving a like and hit the subscribe button so you don't miss each new java tutorial and don't stop here keep up your learning momentum by checking out one of the other videos below thank you so much for watching i really do appreciate you being here with me i'll see you next time