
What will this course be about? I use TDD in my projects almost every day, so I belive I can teach you how to use the method through a real module I created earlier. I had to create a nutrient counting website to help women with gestational diabetes. The user selects a food, then sets the nutrient intake from which nutrient, and then the page calculates the other values. This way you can calculate the rest of any ingredient, so if you want to bring in 20 grams of carbohydrate from white bread, you can see how much you can eat to get it. Because any value can be used to calculate all others, the program can save a lot of time for the user.
The website consisted of a UI and an API. The UI is made in React and the API is in NodeJS using TypeScript. The heart of the API was the nutrient counter module, which I created in a separate npm package. I'm going to rebuild this nutrient counting module from scratch, with your help. So instead of trying to explain in an academic example how you can develop it in TDD in a NodeJS environment, I'll show you on a real project. Work with me and learn how to work effectively with TDD.
Of course, you will encounter more difficult situations during your work that are not covered in this course, but still I think you can learn the basics. If you have any questions during the course, feel free to ask me and I will try to do my best to answer your question! I would also appreciate that if you encounter a more complex task that you cannot develop in TDD, you would describe it to me. I would be happy to expand the course with my suggestion for your example. Good luck and have fun!
The link below (https://github.com/davidcsejtei/nutrition) gives you access to the public github repository, which contains the final result. You can call on the final source code if you get stuck due to an error. In addition, you can always ask me on the Q&A page for the course.
If I start making a new software, I first create a git repository for it.
I always host my projects on Github so open the website and go to your repositories page. Click the green button with the New label and fill out the form that appears.
Enter the package name. I'm going to call it nutrition because we'll calculate the nutrient content of the foods in this example. Write a short description, like A typescript package for nodejs projects and leave the package type on public. Click the Create repository button.
Once the repo is complete, clone it on your machine. Open a console and navigate to where you'd like to download your empty project. This is the Development folder in my case.
Use git clone repo command with the corresponding project url from the github page. The url can be cloned easily by clicking on the small icon next to it. An empty repository warning will inform us that cloning has been successfully completed. Enter the newly created folder and if you did everything right the name of the git master branch after the folder indicates that we are in the git repository.
To get started, open your project in your favorite IDE. I will be using VSCode this time. Good news, we're over the boring part so we can get to work on our project.
In order to write our module in typescript, you must first configure the typescript compiler. You can do this in the tsconfig.json file. At the root of our project, create tsconfig.json.
We need to decide what standard Javascript we want to translate our module into. I recommend ES2019 right now because it is already supported by the NodeJS version we use. So set the target in the compilerOptions section to ES2019. We want the package to be usable in other NodeJS projects, so set the module to commonjs. To strictly require the use of types, the typescript compiler must be set strictly to true. Only in this way can we truly reap the benefits of typescript. We would also like to have our own types available for projects where the package will be used. To do this, set the declaration to true. With this option, the complier will not only create Javascript files for us in accordance with ES2019, but will also create the Typescript files associated with them. Thanks to the definition files, the developers using our module will automatically access our types in the IDE they use.
Also, set allowJS to be false because we only write Typescript files in the package. You can specify that your compilation result be placed in the dist folder using the outDir option.
Add ES2019 to the translation libraries. You can do this in lib. We still need to configure baseUrl, which should be the root folder. There are two more magic settings left, esModuleInterop and allowSyntheticDefaultImports. Set both to true so that your package can work as you wish. I will not go into their meaning now, you will find the TypeScript compiler documentation as an attachment, which describes exactly what they mean. For now, just believe that this is the way to set them up.
All we have to do is tell the system exactly which files we want to compile and which not. In the include section, specify that you want to translate all typescript files ending in .ts. The only exception to this will be files with .spec.ts ending, because they are automated tests. You can exclude these in the exclude section.
Huh, we're done configuring our complier. This is not obvious, I know, but believe me it is worth understanding the exact way it works.
When we make our first compiled version of the package, we will once again discuss the effect of the settings on the output.
Install the dependencies we already know we'll need. Open a console at the root of the project.
Enter the npm init command to create an empty package.json file, which is a must for every package. For input data, feel free to press enter as many times as you need, and we'll fill in whatever we want afterwards.
If you pressed through, npm generated a package.json file at the root of your project.
Open package.json. As you can see, there are still very few things in it. First we change the value of "main" to "dist/index" because our output folder will be dist and the generated entry point of the package will be in index.js. We also know that we will need the build command, as we will definitely want to translate the Typescript files to JavaScript into the dist folder. Rename the generated test command to build, and then specify to run the tsc command, which is the Typescript compiler itself.
Create the src folder for the source in the root. For now, it's empty, but there is the home of our package source code.
Now come the dependencies. Since we want to use Typescript, let's first depend on typescript itself. We also know that we want to use types in a node environment, so install the ts-node package and the @types/node package to get the type definitions for the nodejs core. @Types is a special scope that contains type definitions for many existing systems. It is important that they are all developmental dependencies. If you want to install only the most necessary additional dependencies with these packages, use the --no-optional option to install.
When the installation is complete, check to see if these dependencies have been included in your package.json. As you can see, they got me flawlessly.
We need our next dose of dependencies because we want to use automated tests in our package. We will use the Jest system for this purpose. Install Jest itself, the ts-jest package for the Typescript collaboration, and @ types / jest to have the type definitions for Jest. Of course, these should also be development dependencies, so use the --save-dev switch to start the installation. Once the npm install was successfully completed, we are ready for the next step, configure the test environment.
Configure our test system to work seamlessly with our typescriptes source. To do this, create the jest.config.js file in the root of the project. From this file, we export the following configuration via module.exports:
The first key is to set "globals", including "ts-jest", "tsConfig" with the value "tsconfig.json". This points to our previously created tsconfig file and specifies that this configuration is valid for our automated tests as well.
The next block is called "moduleFileExtensions", enter "ts" as typescript and "js" as JavaScript here.
Our third block is "transform", where we say with a regular expression that for all of our ts-ending files, do the required transformation with the ts-jest package. This is required for our tests to work properly with our typescript source code.
In the "testMatch" section, we also use a sample to specify which files we consider to be test files.
Finally, determine that the tests will run in a "node" environment. This can be specified under the keyword "testEnvironment". This set up the environment for our automated tests.
Let's even add the test run to our list of npm commands. Under "build" you should have a new command called "test" which should contain jest --coverage. This will allow you to start the Jest test system by running all the tests we have written and finally showing a code coverage table as well.
We do not want to upload all files to our git repo. Create a .gitignore file within the project and include the following:
node_modules, .vscode, coverage
In the node_modules folder, we install our Javascript / Typescript dependencies locally, so it wouldn't make sense to commit it. .Vscode contains the code editor meta information I currently use, which may vary from machine to machine, so this is not uploaded as part of the project. The output of the jest tests run on our machine in html format is placed in the coverage folder. As coverage generation is only run on our own machine, the result will not be uploaded. Save the file. From now on, when you want to upload your changes to git, only the relevant sections will be uploaded.
Like .gitignore, we need a .npmignore file. This is different from gitignore in that the files and folders specified here are not included in the npm package that is built from our module. Everything that is added to gitignore is automatically omitted from the npm package, so you don't have to add it again. So we only need to add to npmignore what we want to commit to the git repository but do not want it to be part of our npm package. Such is the src folder containing the package source code. We do not want to install this when using the package because everything we want to make visible to the outside world will be placed in the dist folder when compiling the package. Also add the jest.config.js file to the npmignore because it is only needed to develop the package, not to use it.
We develop our module using the test-driven development methodology. This means that at each step we're gonna write a crashed or failed test case first. Then we add exactly enough code to our module to make this red test turn green. After that, we'll see if we can beautify our code and if so, we'll do it. If we can't find something to refactor, we move on and prepare our next red test case. This process is repeated throughout the development. Use this method to minimize the amount of code written unnecessarily. As specified in the settings, our tests are located in files ending in .spec.ts.
Create the first test file and then the module file. Because we want to count the nutrient content of foods, we need a Food class. So make Food.spec.ts and Food.ts. We usually call test files a spec because they will contain the specification of that file. In this case, the most accurate description of our Food class capabilities can be found in Food.spec.ts. It is very important that the spec files are useful for the development of our module, and when someone installs the package via npm, the spec files will not be installed. We will only install files that are essential for the package to work.
Using jest, we can separate the spec files into two units. With the help of describe we can bring together similar test cases. Using tests, as its name suggests, can define test cases themselves.
Add our first describe block, named Food. You can write tests for the block in the body of an anonymous function. In the first test, we want to check if we can create a Food object. Simply create a constant to create a Food object as its value. Add a check that the value of our constant is not undefined. Of course, we don't have a class called Food yet, but at this point we pretend to exist. This fulfills our operating condition that we must first perform a fail test. Run the tests with the npm test command and you should get the error that there is no class called Food. Super, the second step may be to write exactly as many package code as needed to run this test case correctly.
Switch to Food.ts, create a Food class and export it to can be included in our test. Remember, only write as much code as your last red test turns green. class Food. export default Food. Save and run the tests again. This time the test passed successfully. The third step is always to look at your code to see if you can refactor it. For now, I think it's appropriate, I can't improve it, so we can go on.
Since every food has a name we need to assign a private name property to the Food class. Expect the name value as a mandatory constructor parameter. Following the rules, first modify our first test that we have prepared. We'd like to access the Food name from outside the class using a public getName function. In our test, we check if the name given when creating our object matches what getName returns. Write expect food dot getName dot toEqual
and be the name 'rice', for instance. Enter the same name value expected in the constructor which is nothing other than rice.
Run the test to get a red result.
The TypeScript compiler will give you an error stating that the Food class instance is incorrect. The compiler is right, as there is no constructor in the Food class that can accept a name as a parameter.
Let's modify your Food class and add a name parameter to the constructor. Use the private and readonly modifiers to add our name property with the value you specify as a parameter without any further assignment. Private readonly name string.
Also, add the getName method to access the private name property value outside of the class. getName string return this dotname.
Run tests again.
As you can see, our test coverage remains 100%, which in itself does not mean that our package is flawless, but it provides a good basis for debugging in the future.
Let's introduce another data to Food, which is nothing more than a unit of measure. This is also mandatory data for every Food like name, so we add it as a second constructor parameter. This should also be a string value, for instance g. g stands for gram. Like the name, we need a getUnit function in the Food class, which will return the unit of measure that we have set.
Expect food getUnit dot toEqual g. Also add the unit you choose when you make the food instance.
Run the tests. The result is red, so switch to Food class and add a string called unit to the constructor parameter list. Private readonly unit string. In addition, build the getUnit method, similar to getName. getUnit string return this dot unit.
Run tests again. Everything is green, which means we have successfully added Food implementations with name and unit information.
So far we are doing very well, we already have two pieces of information in our Food implementation. Beyond these, we need a portion of a number that represents the nutritional value of a Food. In our specification the nutrient value consists of five values, the amount, the carbohydrate, the protein, the calories and the fat. We will provide the nutrient values with an object inside the Food called baseValues. Although baseValues is an object with several different attributes, we will not add it to Food in one step. We move from attribute to attribute until all the required values are added to the baseValue object. Const baseValues equals empty object. Add baseValues as third parameter to the Food constructor called baseValues.
First, you need an amount value, that is, the first property of baseValues is an amount. Add a constant value of 100 and verify in our test that the value of baseValue of the created Food object is 100. Expect food.getBaseValues().amount toEqual 100. Run the tests. The result is a failed run, so switch to your Food class and make the necessary improvements. Add baseValues to the Food class as a private readonly parameter at the end of the parameter set, so private readonly baseValues.
The question is what type of variable should baseValues be. So far we have only string type parameters, which is a simple type. However, for baseValues, we need a composite type that will be an object with specific attributes. Let's create a custom type called Nutritions. Create the Nutritions.ts file. Since we will only define one type in this file, we will not create a separated spec file for it. Of course, the type will be tested the same way only through our Food class not with its own spec file. Write type Nutritions equals an empty object. In our new Nutritions type we need an amount value as our test case defined. Amount. Amount stands for the amount of the Food, so it's a number. Don't forget to export the type at the end.
Go back to the Food class and define Nutritions as the baseValues type in the constructor. Nutritions and import nutritions from nutritions. Run tests again. It's still failed, because the getBaseValues function is still missing. Go to the Food class and write getBaseValues with the return type Nutritions, and return this.baseValues. Here comes annother test running. Cool, it became green which means the baseValues with the first component amount is added properly.
Repeat the same process in the case of fat, carbohydrate, protein and calories components of the baseValues. Extend baseValues in the test with fat, it should be 30. It's a number value. Create a new expect under the last one. Expect food.getBaseValues().fat dot toEqual 30. Run tests. Failed, so switch to the Nutrition type and add fat as a number as a second property. As you may notice, after added a fat value to the constaint baseValues in the test the IDE gave us a warning message. That's because VScode read the type Nutritions and saw there is no fat property in it at all. This is the compile time type protection TypeScript provides us. It's useful for others as well who want use our module as a NodeJS package in the future. Important to remember, this type protection works well only if you compile your package with the strict true option in your TypeScript configuration. If you insist on strict typing, you will not only be signaled in the IDE in such cases, but the TypeScript compiler will not compile the package, but will stop with an error message. Run tests once more. We're good.
Do it again in the case of carbohydrate. Add carbohydrate with a random number value in the test, and write expect food dot getBaseValues() dot carbohydrate dot toEqual to your random number value. Run tests and fix the error. Carbohydrate number. Test again.
Protein and calories values remained. While we have to go through the same process for these than before, we can add the remaining two parts at the same time. So add protein and calories property to baseValues. Then come the tests, namely expect food dot getBaseValues () dot protein dot toEqual 65 and expect food.getBaseValues () dot calories dot toEqual 124. Run the tests. As expected, the tests failed, so we can add two new properties to our Nutrition type, protein and calories. Write protein number and calories number. Run the tests and be happy with the 100% result.
Implementation of the nutrient values for food has been completed. If we need to make any changes to our implementation in the future, we can do easily because the high test coverage has drastically reduced the number of potential errors that the changes could bring. Let's move on and see if we need any error handling in our current state.
Our goal is to give as much help as possible to the other programers who using your package . One of the basic prerequisites for this is the proper error handling. To do this, let's look at what errors we should deal with in our package the current state. If you are not experienced in TDD methodology, you can ask "how the hell does error handling fit into the methodology?". My answer: Just like other improvements. First we write a test in which we expect an error then add the error handling to the product code.
Before programming anything, let's talk about what do we consider an error in our system? To answer the question, let's look at the data we're working on. On the one hand, there is a name for each Food that we'd like to see as a mandatory field. In runtime, we want to check that the given food name is not empty. If our Food instance gets an empty name, we throw an error, which can be checked by the project which is using the package. I consider it a good practice to create unique Error classes for our own errors. They provide the most useful information for the outside world.
One important technical note: we are able to write a test to test whether a program is throwing error at a particular point or not. In the Jest test framework it only works well if the code snippet to be scanned is encapsulated in an additional code block. We're gonna use an arrow function as a wrapper to solve this problem as you will see. So far, we have written test cases where we have modified an existing test until we have achieved our goal. This time, however, we need the tests we wrote earlier, in addition to writing new ones. This is because we also want to add error branches next to the positive branches we coded before. This is the moment when you can learn which cases do you need add more tests instead of modifing the existing test cases. Ask yourself the following question: do I need all of the test cases I had until this time AND a new one? If the answer is YES, create a new test case. If it's no, just modify one of the existing test cases.
Create a new test block called "create food with empty name". test create food with empty name and an arrow function. Copy baseValues from the previous test case, which is also a mandatory field for the Food class. Select the baseValues, copy that, and paste it into the new test case. Here comes our wrapper arrow function I just mentioned before, this is just for the sake of Jest. The new Food comes here with the empty name and the unit is g and finally baseValues as a last parameter. Expect arrow function new food empty name g and baseValues dot toThrowError EmptyFoodNameError. Of course, the EmptyFoodNameError does not yet exist, but according to TDD rules, let's run the tests now to see if this is why our test failed. npm tests.
Our test run failed due to missing EmptyFoodNameError, as expected. Create an empty errors folder under src and create the EmptyFoodNameError.ts file as well. errors. create new file, EmptyFoodNameError.ts.
All of our errors will be derived from the built-in node Error class. The Error class has a constructor parameter called message that we can use to set our own error text. export default class EmptyFoodNameError extends Error constructor message equals Empty food name is not allowed. In order to work properly, we also need to call the constructor of the parent class, in this case the Error class. super message. We've completed our first unique error. Now, add the logic to the Food class to look for the name value when creating a Food instance. If you try to create a Food instance with an empty name, the EmptyFoodNameError error you created earlier will be dropped. Let's switch to Food class and go to the constructor. If name dot length equals zero throw new EmptyFoodNameError. *Don't forget to import the error class, write import EmptyFoodNameError from dot slash errors slash EmptyFoodNameError. Go back to the test suite and do the import here as well, so import EmptyFoodNameError from slash errors slash EmptyFoodNameError.* Rerun the tests and if you did well you will see a green result this time.
In the previous section, we handled the issue of trying to create a Food instance with an empty name. In our solution, the program will throw an error with an empty name. The next possible mistake is if you want to make a zero or negative amount of food. It doesn't make sense, so in this case we have to give an error as well. Create a new test case called create food with zero amount. test create food with zero amount and the empty function after that. As the name implies, here we want to test the case where the amount value is zero. Copy baseValues from the previous test and write the amount to zero.
Expect empty braces arrow function new food rice, g, baseValues dot toThrowError InvalidFoodAmountError. The steps will be the same as in the previous test, when we created EmptyFoodNameError and connected it to Food. Run the tests. The result is red, meaning we have set new expectations that our code currently does not meet. Create an InvalidFoodAmountError.ts file in the errors folder next to EmptyFoodNameError. Again, the built-in Error class will be extended. export default class InvalidFoodAmountError extends Error. We need a constructor with a single parameter that is nothing more than a number represents the invalid amount value. Call the parent constructor super. In this error, we also know in advance what text we would like to see as a message, but there is a variable value, the amount as well. So call the constructor with a string literal, Invalid amount dollar sign amount. It must be a positive number.
Let's move on to the Food class and use our newly created Error class. As with our previous Error, we're gonna check the parameters in the constructor to see if we got a positive value for the instance. If the amount is not positive, throw an InvalidFoodAmountError. if baseValues dot amount is less or equals zero throw new InvalidFoodAmountError amount. For this error, we use the amount that is currently incorrect to send the most eloquent error message back to the caller.
Import the error class into the Food class. Import InvalidFoodAmountError from slash errors slash InvalidFoodAmountError. Go back to the Food test and import the error class there. Import InvalidFoodAmountError from slash errors slash InvalidFoodAmountError. All right, now let's run the tests. The test result is green so we have successfully introduced a new error branch in our module.
We have completed two validations that are used in the Food class constructor. It is immediately apparent that the length and complexity of the constructor have already increased and that if further similar validation steps are needed in the future, this process will continue. In order to keep our code as legible as possible, we organize the validation steps into functions. Because we have tests for expected behavior, we can, in principle, easily check the results of our refactoring. After the modification, run the tests to make sure that the previous expected behavior has not been broken.
So, let's look at the first validation step in the Food class constructor. You can simply copy the section from tenth to twelfth line and paste it into a new function within the class or use the IDE refactor function. I choose the latter. I select the code section to extract, press the right mouse button and select refactor. I choose 'extract to method in class Food' and enter the name of my new function: validateFoodName. I run the tests to see if my code works the way it used to. All of my tests are green, so I successfully extracted the first validation. Repeat this step for the second validation.
Select, right click, refactor, 'Extract to method in class Food', 'validateFoodAmount'. Let's look at the tests. The result is green again.
If you look at the Food class now you can see that on the one hand the code has become more readable by cleaning up the constructor and on the other hand we have made our validation steps within the class reusable. We could do it with confidence because we have automated tests.
The purpose of the package is to calculate the nutrient content of different foods based on their base values. Basic nutrient values have already been added to the Food class. The default values will always be needed, and you do not want to change them after you set them when creating your food. In order to store the currently calculated values, while maintaining the default values, add a new object to the Food class called CurrentValues. CurrentValues has the same structure as BaseValues. The content of CurrentValues is initially the same as BaseValues, and when the user changes one of the nutrient values, the other values must be recalculated accordingly and stored in CurrentValues.
The next step is to jump to the tests and decide whether you need a new test case or whether you need to modify an existing one. We would like to introduce the concept of currentValues, which must always exist, because without it we do not want to handle any Food object. This means that we need to modify an existing test case. If we would create a new test case, we would also deal with a case where the currentValue is not defined. We don't want to do this.
So let's look at the tests. The last two cases cover error branches. So 'create food with zero amount' and 'create food with empty name' still need us in the same form. Checking for the existence of currentValues must be included in our first positive branch test case, 'create'. This time we do not want to cover a new error branch, but a valid case therefore we need to complete the first test case. Why? Because it tests the creation of a valid Food object. The test checks all mandatory values defined so far.
Add a new check to see if the currentValues value of a Food object matches its baseValues. This is the behavior we expect of all objects in our Food class. When someone uses the package and modifies a nutrient parameter, the modification will be made in currentValues and the recalculated values of the other nutrients will be saved in currentValues. We have to ensure that you always access to all the initial values that you have set in baseValues, so that you can calculate the current values at the same time.
expect food dot getCurrentValues dot toEqual food dot getBaseValues. We treat currentValues as a private data member in the Food class just as we do baseValues, so we do a getter function to access it outside of the class. Let's run the tests. Broken because the getCurrentValues function does not exist. Let's go over to the Food class and create it. getCurrentValues with return type Nutritions return this dot currentValues. We need to create a currentValues data member similar to baseValues. The difference, however, is that we now separate the definition of currentValues into the class and get the value in the constructor. private currentValues Nutritions. Because our goal is to match currentValues with baseValues when creating an instance, we write this.currentValues equals dot dot dot baseValues. Rerun the tests and if you did well you will see a green result this time.
We already have basic nutrient values and current nutrient values in our Food class. At the moment, these two objects are exactly the same because currentValues gets the baseValues value when creating each object in the constructor. We want to be able to change any of the current nutrient values of a Food instance once that object has been created. First, let's modify the current quantity, that is, the value of the currentValues amount property.
Make a new test case. test create food and change amount. Make an arrow function for the body of the test. Copy and paste the baseValues and create the food object. Select the lines, copy and paste here. Now change the quantity by calling the changeAmount function. food dot changeAmount twentythree. Finally, verify that the amount has been updated to 23 of the current nutrient values. expect food dot getCurrentValues dot amount dot toEqual twentythree. Run the tests. The red result indicates that we called a function that does not yet exist.
Switch to the Food class and add the missing changeAmount function. changeAmount amount number this.currentValues.amount equals amount. Rerun the tests, and since we're green we can move on.
As with basic data, only positive values are accepted for current data. Therefore, we further develop our changeAmount function with a validation. We will use the same validator as for baseValues. Fortunately, we have extracted the validation into a separate function, so all we have to do now is modify it a bit so that it can be used equally in both cases.
Create a new test case to see what happens if you want to change the amount of food to a negative number. test create food and change amount with negative number and arrow function. Copy the lines containing baseValues and the creation of the new Food from the previous test. Select, copy and paste. expect arrow function food dot changeAmount minus twentythree dot toThrowError InvalidFoodAmountError. Run the tests to get the red error. It says Received function did not throw. You're right, let's go over to the Food class and add this upgrade. Please modify our validateFoodAmount function to exclude baseValues, as we will now call this validator in two different locations.
Replace baseValues with amount in the parameter list. Change the type of amount to number. Also replace baseValues.amount with a regular amount in the body of the function. To make the validator work well at its previous location, change the value of the validateFoodAmount function in the constructor to baseValues dot amount. We now have a universally usable amount validator that we can use in our changeAmount function. this dot validateFoodAmount amount. By running the tests, you can see that you have successfully added the validation to the amount change.
We can change the actual amount of our food. Now, using this ability, we need to calculate other nutrient values for the changed amount. The calculated values are also stored in currentValues.
Before programming anything, let's think about what we want to do exactly. For all foods, we have basic nutrient values for calculation at baseValues. Let's see an example use case. Suppose the user has selected eggs as food in the user interface. This will create a Food object in our module with the egg name and its associated baseValues nutrient values. Now take an example where the user changes the quantity. The basic amount is 100 grams, changing it to 87. Then you have to automatically calculate the remaining 4 values, namely calories, carbohydrates, protein and fat.
How do we calculate these? Let's start with the calories. calories equals amount multiplied by calories divided by amount. More specifically, currentValues.calories is equal to currentValues.amount multiplied by baseValues.calories divided by baseValues.amount. At the end, the result is rounded to the nearest whole number. Each time the user reduces or increases the amount, this calculation is performed to calculate the actual calorie intake. Returning to the example, if the user changes the amount to eighty-seven and the base calorie value for the egg was one hundred and thirty, with the amount changed, the new calorie value would be eighty-seven times one hundred and thirty divided by one hundred, which should be equal to one hundred and thirteen. The result will be the new value for calories, that is, currentValues dot calories.
Okay, we've clarified what functionality we want to implement, let's go back to the editor and program it.
We know the formula for calculating the actual calorie content, so we can implement it using the TDD method. Add a new test case to our test file called 'create food, change amount and calculate current calories'. This may seem a bit long, but always try to be clear in the test names. When you have hundreds of test cases, every little help comes in handy, which increases the transparency of your tests, such as good test names.
Copy the food creation part of the previous test case with baseValues and paste it at the beginning of the new test case. All we need to do now is call the food.changeAmount function to modify the current amount value for our food instance. food dot changeAmount eighty-seven.
Next, we expect the currentValues calories property to have a new value automatically calculated using our formula.
expect food dot getCurrentValues dot calories dot toEquals. Okay, but what exactly is the new value we want to see? Quickly calculate the baseValues used in our test case and the amount eighty-seven given by changeAmount.
So, first we need to multiply the calories of baseValues, which is currently one hundred twenty four with the currentValues dot amount of eighty seven, as modified by the changeAmount function. We need to divide this result by the baseValues dot amount, which is one hundred in our case.
The result is one hundred and seven point eighty-eight, but we agreed to round it up with the help of the Math.ceil function. This means that the expected new calorie value should be one hundred and eight.
Run the tests to see our test case fail because we get one hundred twenty four instead of one hundred eight as the actual calorie value. This is because we haven't implemented the calculation yet. Switch to Food class and add the calculation at the end of our changeAmount function using our formula.
this.currentValues.calories is equal Math dot ceil - I just start a new line - this dot currentValues dot amount multiplied by - here comes annother new line, because this formula is long - this dot baseValues dot calories divided by this dot baseValues dot amount. I just need to make some code style adjustments here with some new lines. It's okay. By running the tests this time, you need to get a flawless result. Yes, it's green.
Before we go any further, let's take the formula of our calculation into a class-level method. This increases the readability of changeAmount. Since we have a test for this code snippet, we can refactor it without having to worry about changing our current behavior.
Create a function called calculateCaloriesFromAmount. Move the calculation from changeAmount. Select, cut the code from changeAmount and paste it into the new function. Don't forget the return keyword before the calculation. Finally, call the function within changeAmount. This improves the readability of our code. Run the tests to make sure they remain green. If so, we can go on. Write npm test and wait for the result. It semms to be okay, we can move on.
Let's imagine a situation where we want to change a code snippet for some reason that was already made and works well. We have made it mandatory for each instance of our Food class to have a unit parameter besides the name. This unit parameter has not been used anywhere in the package yet. We don't use it inside the package, but we can make possible to get information about the unit of the food for the users which the nutrient values are meant. It may be liter, gram, kilogram or any unit of measure that we associate with the particular food. So far we have expected this parameter as plain text, which also means that any user of our package could have entered any text. Since we know that we only want to allow this value from elements of a given set, it makes sense to redesign our code. Using typescript allows us to break code that would violate this restriction during compile time. It is important, however, that if the unit parameter is somehow invalid for runtime, we cannot capture it by this method. However, you can create a useful constraint using the Typescript type system so that at least other programmers see what we want to allow there.
Let's look at our first test where the unit value is listed. Create a new enum type and name it 'Units' in the 'Units.ts' file. In this enum we collect all possible food unit values. For now, we put only one value in it, the gram. But first, let's change our test to wait for a Unit value instead of a string. Delete the g string and write Units dot GRAM. Run the tests to see if they are really broken. Yes, so let's create the missing stuff. First, create the Units.ts file. enum Units braces, GRAM equals 'g'. Export default Units. Remember to export the enum, otherwise you will not be able to access it from other files. Go back to the tests and import the new enum. import Units from dot slash units. Rerun the tests. The tests are green, but we haven't really changed the constructor of our Food class. This is a common and very dangerous mistake when working with TypeScript, so you should pay close attention to it. Let's go to the Food class and change the constructor parameters there too. Here again, change the unit type to Units enum from string. Import Units. Import Units from dot slash Units. Run the tests again. The result is flawless, but there's one final step left. In all tests, replace the unit parameters so that your code remains consistent. Delete string g and add Units.GRAM. Repeat for the remaining 4 test cases. Units.GRAM, Units.GRAM, Units.GRAM and Units.GRAM. Run the tests when you're done. We're green again, but this time we're really done with the change you want.
We would like to calculate the actual value of other nutrients similarly to calories when the amount changes. The formula we're going to calculate is almost the same as the calorie formula, but there are some changes. The difference between these calculations is one element of the formula, namely the baseValues default value for the current value to be calculated. Using this knowledge, we can improve our formula so that it can be used to calculate each nutritional value without having to make a separate formula for each one. Let's get started.
It is up to you to check the values to be calculated in separate test cases, or to refine the last test case and look at the correctness of the calculations. I choose the latter, that is, converting the test case in which I checked the actual calorie value. First I rename it because I'm not just checking the calories in it, but all the values, the new name is 'create food, change amount and calculate current values'. Of course, I need to know what values I expect from the calculations for each nutrient value.
The fat is calculated as follows: The base value for fat is thirty. Using the formula above, the current amount is eighty seven times the fat is thirty and divided by the amount base value of one hundred. This is twenty-six point one which is rounded to twenty-seven. the actual fat value for eighty seven grams is twenty-seven. So do the calculation for the other nutrient values. In the case of carbohydrates, this is eighty seven times forty divided by one hundred, which is rounded thirty-five. The protein is eighty seven times sixty-five divided by one hundred, which is rounded fifty seven. The calories remains which is eighty seven multiplied by one hundred and twenty-four divided by one hundred. The calories new value is one hundred and eight. Add all the expected values to the end of our test.
expect food dot getCurrentValues dot fat dot toEqual twenty-seven
expect food dot getCurrentValues dot carbohydrate dot toEqual thirty-five
expect food dot getCurrentValues dot protein dot toEqual fifty-seven
We have the expected values, now let's run tests. Our tests ended with a red result, as we have not developed the necessary additions yet. Let's look at the Food class.
Transform the calculateCaloriesFromAmount function so that it can calculate the actual value not only for calories but also for other nutrients. First, rename it to calculateNutritionFromAmount and add a string type parameter called nutrition. This will dynamically identify the required value from baseValues. Modify this dot baseValues dot calories with this dot baseValue square brackets nutrition. This way, the formula will always use the nutrition value obtained as a parameter, which will be either protein or fat or carbohydrate or calorie. As you can see, Visual Studio underlines that I typed the baseValues variable of Nutrition incorrectly according to Typescript. This time, the IDE is right, because to enable square bracket indexing in a class or type, you need to add the key for that type. Open Nutritions and write: square brackets key colon string square brackets closed colon number. Save the file and go back to Food. We have redesigned our calculator function properly, we just need to call it with the appropriate parameters. Replace the calculateCaloriesFromAmount call with this dot calculateNutritionFromAmount and set the calories as a parameter. This is how we calculate the actual calorie value like before, but using the universal calculator function this time. Add the calculation of the other values in the same way.
this dot currentValues dot fat equals this dot calculateNutritionFromAmount and fat as a parameter.
this dot currentValues dot carbohydrate equals this dot calculateNutritionFromAmount and carbohydrate as a parameter.
and finally
this dot currentValues dot protein equals this dot calculateNutritionFromAmount and protein as a parameter.
Run the tests, because once you have put it all together, this time you will calculate all the other nutrients correctly with the change of the current amount.
We have created the logic in our changeAmount function, which is correctly calculate other nutrient values as the amount changes. It is obvious that for the other nutrients we call the same calculator function, but with different parameters. We are refactoring this part because we can do it better. Luckily, we have the test we created to check the previously expected results after refactoring. Remember that refactoring is an important part of the TDD methodology, and we're practicing that now.
Create a new function called calculateNutrients, which waits for an array containing strings. We use this function to calculate the actual value of the nutrients received as a parameter, one at a time and add the result to the currentValues.
This function can be private, as we only want to make it available within the class. private calculateNutrients nutrients string array.
Let's iterate through the array that is given as a parameter, so write nutrients dot map nutrient arrow function. At each iteration step, we can calculate the current value of the given nutrition. this dot currentValues nutrient equals this dot calculateNutritionFromAmount nutrient. With this short code snippet, we can replace four function calls within changeAmount. It is important that with this solution we can gain even more in the future if we want to add more nutrient values to the Food class. Call calculateNutrients at the end of changeAmount as follows: this dot calculateNutrients and the new array of calories fat carbohydrate protein as a parameter. Delete the four separate function calls used so far, as we replaced them with the new solution. Select, right click and cut and remove the remaining new line as well. If you ask me, it looks much more professional now. I just break this line to make it more readable for you. If your refactoring is correct, you will still get green when you run the tests. We can reasonably assume that refactoring has not ruined our previously well-functioning code snippet.
If the user changes any other value than the amount, we calculate the other nutrient values in two steps. In the first step, we calculate the amount based on the changed value and store the result in the current value of amount, and in the second step, calculate the other nutrient values based on the current value of amount.
Let's look at an example where the calories are changed by the user. Let the new value be two hundred and eleven. After the calories changed, the first step is to recalculate the amount. Then, using the new amount, we can easily calculate carbohydrate, protein, and fat. The algorithm for the computation required in the second step has already been programmed earlier and will be used again this time. We need to make a solution for the first step, as we have not made such a calculation yet. Let's look at the details of the first step.
So we want to calculate currentValues dot amount, which is equal to currentValues dot calories multiplied by baseValues dot amount divided by baseValues dot calories. Remember that we want to round up the result, so we wrap the whole sequence of actions into a Math dot ceil function. Add concrete numbers in our example. The new calorie value is set by the user to two hundred and eleven, multiplied by the initial amount, which is one hundred and divided by the basic calorie value, which is one hundred and thirty. The result is one hundred and sixty-three. After calculating the first step, we can go on to calculate the value of all other nutrients.
The calories changed to two hundred and eleven, which led to a recalculation of the current amount to one hundred and sixty-three. In the first half of step two, recalculate the carbohydrate. We also use rounding here. Math dot ceil. CurrentValues dot amount multiplied by baseValues dot carbohydrate divided by baseValues dot amount. The numbers from the example should be hundred and sixty-three, four hundred and fifty and one hundred. The result is seven hundred and thirty-four, which means that currentValues dot carbohydrate is equal to seven hundred and thirty-four.
There is still protein and fat calculations left. These can be calculated using the same algorithm as carbohydrate. The only difference is that in multiplication, baseValues dot carbohydrate has to be replaced with protein or fat. For protein, the calculation is one hundred and sixty-three times one divided by one hundred, which is rounded up to two. The value of currentValues dot protein is two.
Only the fat value left. hundred and sixty-three multiplied by four divided by one hundred. The result is seven, so the currentValues dot fat is equal to seven.
I hope it's clear, we're gonna code the solution in the next section.
In the previous lesson, we agreed to divide the calculation of nutrient values into two parts if one of the nutrient values changes. First, we calculate the new amount based on the changed nutrient value, and then adjust it to calculate the remaining nutrient values. First, let's look at the case where the calorie is changed by the user.
Following the TDD rules, we first create a new test case. I call it 'create food, change calories and calculate current values'. test 'create food, change calories and calculate current values'. We need a food instance and a baseValues, so copy them from the previous test case. Select, copy and paste. For simplicity, change the values of baseValues in the previous section to the values used in the drawing. The amount should remain one hundred, fat is four, carbohydrate is four hundred and fifty, protein is one, and calories one hundred and thirty. At this point, change the calories value by calling the changeCalories function. This function does not exist yet, but we will create it soon. Let the new calories be two hundred and eleven, as we drew in the previous section in our example. In this test, we need to verify that the currentValues values change correctly by changing the calories. First, check the calories value itself to see if it has changed to two hundred and eleven. expect food dot getCurrentValues dot calories dot toEqual two hundred and eleven. You need four more copies of expect so copy and paste this row four times. In the second, we look at the amount instead of the calories, which should be one hundred and sixty-three this time as calculated in the example. The third value is fat, which we expect to be seven. The next value is carbohydrate, which, after recalculation, should be seven hundred thirty-four and eventually the protein should change from one to two. Now that we have completed our test case, run the tests for the result. Our tests failed because the changeCalories function in the Food class is missing. Change to that file and add the missing parts.
The test result indicates that the changeCalories function is missing. Add it to the Food class. changeCalories calories number. First, change the currentValues dot calories to the new value based on the parameter. this dot currentValues dot calories equals calories. Now comes the first step of our algorithm, which is to calculate the amount based on the new calories. this dot currentValues dot amount equals this dot calculateAmountFromNutrition calories. This will also be a new function, create it inside the class. private calculateAmountFromNutrition nutrition string. This function can be used to recalculate the amount by changing any nutrient value other than amount. This will be very useful in the future. Based on the calculation above, return Math dot ceil this dot currentValues nutrition multiplied by this dot baseValues dot amount divided by this dot baseValues nutrition. With this we calculated the first step.
Fortunately, with the previously created calculateNutrients function, we can recalculate any nutrient value based on the current amount, so all we have to do is to call this function on the remaining nutrient values. We have already dealt with calories and amount, so we still have to calculate the fat, carbohydrate and protein values. this dot calculateNutrients fat, carbohydrate, protein. Ok, we are able to determine what other nutrient values came from the food you choose for a given amount of calories. Finally, run the tests and you should get a green output this time.
When you need to create several similar test cases, you might want to organize them into a describe block. This is useful because we can share common code snippets between test cases. In our case, we have similar tests work with the same food instance, so we can organize them under one describe. describe create food and change values and an empty function. What should be common in these test cases? The food object itself, so we add it to the describe block. let food Food. There is a special Jest function will run automatically before each in-block test case. It's called beforeEach. beforeEach and an empty function and copy the baseValues and food instance from the first test case here. Select, right click and copy, go down to the new case, and right click, paste. Remove the const keyword from food, as we've already defined the variable before. Great, now copy the previously created testcase into the new describe block. Select the test, cut, remove the empty lines then go down and paste. Delete the 'create food' words from the test case name. Delete the parts you have added to beforeEach, as those parts will automatically run before the tests, you don't have to type them into each and every test cases.
Run the tests to see if something went wrong or not. Our tests are still green, so we've done a good job so far. A little refactoring can fit into this test case. Organize your query for currentValues using the JavaScript deconstructuring feature in a separate row so const calories, amount, fat, carbohydrate, protein equals food dot getCurrentValues. Use these values to replace the queries for each expect. This makes our code much more readable and effective. You have to get rid of the first two elements of the call because they have become redundant. Run the tests and if they are green we can go further. We will supplement this describe block with the tests needed to change other values.
Like changing calories, let's make the fat changes possible. In our reorganized tests, you can easily add a fat change test case.
test change fat and calculate current values. food dot changeFat twenty.
Since we outsourced the common code snippets earlier, we can now save more lines. This time we don't have to code the creation of the Food object, since it is already in the beforeEach.
Query the current nutrient values using a deconstructor as before.
const calories, amount, fat, carbohydrate, protein equals food dot getCurrentValues.
We just need to check the values of currentValues in the same way as we did in the previous test case.
expect fat toEqual twenty. expect amount toEqual five hundred. expect calories toEqual six hundred and fifty. expect carbohydrate toEqual two thousand two hundred and fifty. expect protein dot toEqual five.
Now that you are ready for testing, run the tests. The tests becames failed because the changeFat function in the Food class is missing. To solve the problem, add this function to the class. It looks very similar to changeCalories in form and content as we use the same algorithm as there. The only difference is that this time we calculate the actual amount based on the actual value of the fat and then use the new amount to calculate the other nutrient values.
changeFat fat number. this dot currentValues dot fat equals fat. this dot currentValues dot amount equals this dot calculateAmountFromNutrition fat.
And finally this dot calculateNutrients calories, carbohydrate and protein.
So first we set the new fat value to currentValues, then we recalculate the current amount with this value and finally we recalculate the other nutrient values with this new value.
By running the tests again you should now get a green score.
Because we want to calculate all the nutrient values, we need to create similar changeProtein and changeCarbohydrate functions, even for protein and carbohydrates. We're going to do these two together now, because I don't want you to getting bored because of the repeating tasks.
So, copy your last test case and rename it to 'change protein and calculate current values'. Change the changeFat call to changeProtein, with parameter one hundred and three. Change the expectation slightly. In the first place, check for protein, which is expected to be one hundred and three. Follow this with the amount first recalculated. Calculate the expected new amount with the change in protein. We can calculate this by multiplying the value one hundred and three of the new protein by the original amount of baseValues, which is one hundred, and dividing by the value of the original protein, which is one. The result is ten thousand three hundred, that is, the currentValues dot amount must be that. Other values are calculated using the same method. The new value of fat is equal to the old value of fat multiplied by the value of the new amount divided by the value of the old amount, so four hundred and twelve. Let set the result as the new value of fat. The new value of calories is nothing less than the new amount multiplied by the base value of calories divided by the base value of amount which is thirteen thousand three hundred ninety. Set this result as the calories expected value. And there's only carbohydrate left, which is the new amount multiplied by the original carbohydrate divided by the original amount, that's forty-six thousand three hundred and fifty.
Now that we have completed our test case, let's run the tests according to the rules of TDD. We get a red result, even though the changeProtein function is missing. Let's switch to Food class and add the missing part.
Our changeProtein function will be almost the same as the previous change functions, but now we are going to work with the protein value. First we set the currentValues dot protein to the new value, then calculate the new amount, and finally recalculate the remaining nutrient values. We talked about the details of this algorithm before, so I won't go into it again, just write the code. this dot currentValues dot protein equals protein. this dot currentValues dot amount equals select, copy and paste the calculateAmountFromNutrition and change the parameter to protein. Also copy the last step of changeFat, which is to call this dot calculateNutrients function. Change the last element of the parameter array, because this time we want to calculate fat instead of protein, among other values. This completes the changeProtein function. It is clear that it is very similar to changeFat, only it differs in its parameters. This was our goal, because the more similar code snippets you can generate for different functionalities, the more likely you are to be able to merge them in part or in full later. With this approach, you can write smaller, more transparent, and even as reusable code as possible in your projects. Run the tests. If you worked well, the result is green.
We have to do the same work for the carbohydrate value. Let's quickly run through the process. Copy the previous test case and rename it to 'change carbohydrate and calculate current values'. Instead of changeProtein, we will now implement changeCarbohydrate. Let the value of the parameter be eleven. Reorder the expectation to start with carbohydrate. Take the amount to the second place. Obviously, carbohydrate should be eleven. Remove the other expected values to ensure that we won't forget to refresh all of them. The amount should be the next. Let's see what the new amount should be, it's eleven multiplied by one hundred divided by four hundred and fifty, so it's three. Change the expect of the new amount value to three. The expected new value of the protein equals the new amount multiplied by the original protein divided by the original amount, zero point zero three, which is rounded to one. The new value of fat equals new amount multiplied by the old fat divided by the value of base amount, that is zero point twelve, which is also rounded to one. Finally the new value of calories is equal to the new amount multiplied by the old calories divided by the base amount so the result is three point nine, rounded to four. Set the expected value for calories. We are done with the expected values, now run the tests. The test fails because the changeCarbohydrate function is missing.
Open the Food class and add the missing function. This time, feel free to copy the entire changeProtein function as you know exactly what you need to do. Rename the function from changeProtein to changeCarbohydrate. Also rename the parameter. In the first line, set the new value to currentValues. Then calculate the new amount from the carbohydrate, so change the parameter name to carbohydrate of the calculateAmountFromNutrition function call, and finally recalculate the other nutrients, fat, protein, and calories. Run the tests again, and if you get a green result, you have successfully completed your package with other nutrient modifications.
Before we can release the first version of the package, we need to make parts of it available to the outside world. We can do this by exporting. The entry point for the package will be the index.ts file, which we have not yet created, so lets create it. index.ts. Any element of the package that you do not export through index.ts will be inaccessible to the project using the package. If you look at package.json, you will find dist / index as the entry point under the main keyword. This is because the translated finished package will be found in the dist folder. Let's see what items we want to export. We will export by first importing everything and then exporting all important items.
We definitely need the Food class, so import Food from dot slash Food. In the Food class, we have our main logic. We also need the Nutritions type, import Nutritions from dot slash Nutritions and the Units that have the units, like Gram. Import Units from dot slash Units. In addition, we have defined two types of errors, EmptyFoodNameError and InvalidFoodAmountError. Also import these, import EmptyFoodNameError from dot slash errors, slash EmptyFoodNameError, and import InvalidFoodAmountError from dot slash errors slash InvalidFoodAmountError.
With this we have imported all the necessary items, now here comes the export. Remember, you will not be able to access these items in the project using the package unless you export them from the package.
export brace Food, Nutritions, Units, EmptyFoodNameError, InvalidFoodAmountError. We're done with our index file. Let's move on to the release process.
Congratulations, the package is ready. Now let's share it with the world, that is, release the first version.
First, let's do a production build. You can do this with the following command: npm run build. We have previously configured package.json to run tsc, which is a TypeScript compiler, as a result of this command. This process will create the dist folder for the package release. When you look at the contents of the dist folder, we have all the source files, including the associated .d.ts definition files. This is to ensure that our types can be used correctly by the program using the package. So we have build, now let's specify the current npm version for the package.
The numbering is up to you, now I'm following the semantic versioning, this is why we set the default version to 1.0.0 in the package.json file earlier. It is important to upload your own packages under your npm username as this will ensure that there is no name conflict with other global packages. To do this, add your npm username to the front of the package name like this. The username used on npmjs.com must be here. If you don't have one, go to the npmjs.com website and create one quickly.
Although we manually configured package.json at the beginning of our project, the version is 1.0.0. so, enter the command to set the initial version: npm version 1.0.0 --allow-same-version. This is important because even though the version has not changed, the correct git tag for our project was created automatically. By the way, If you want to upgrade the patch version, use the npm version patch, if the minor is npm version minor and if major, use the npm version major commands. Note that the npm version command will give you an error if your project has a local change that you have not committed. Therefore, always do a git commit before call the npm version command.
Just to try the npm version patch command, I'm going to upgrade the version right now, of course you don't have to do this, you can release 1.0.0 first. npm version patch. As you can see now, npm has upgraded to version 1.0.1. It also automatically created the 1.0.1 git tag. At this point, I'm going to release version 1.0.1 from the package. It is very important that if you practice, there is a restriction in the official npm registry that once you release a version of a given package you will not be able to re-publish it, even if you delete it from the registry first, using the npm unpublish --force command. This is important during practice when trying package releases. I suggest you to give your package a new name in all such cases to avoid this annoying phenomenon.
Before publishing the package, add the .vscode and coverage folders to the npmignore file. Npmignore contains folders and files that we do not want include in our package. These files are only needed during development, so if you want to use the package in your project you don't need them. Dot vscode and coverage.
Publish the package by running npm publish --access = public. The 'access' switch is needed because if you want to publish a package under your own user name, by default it will be published with private access. The only problem with this is that publishing a private access package is a paid service for the npm registry. Since we do not want private access but want to publish the package under our name for free, use the access public switch.
npm publish --access = public. If all goes well you will see the package version number at the end of the upload. Above is a list of all the files contained in the current version of the package. If you look at your package.json file, you will see which version you have released. I have 1.0.1 because I did a patch version upgrade.
Log in to npmjs.com with the username which you uploaded your package under. Click on the top right icon to navigate to the Packages menu and inside the nutrition package. As you can see, I have the latest version 1.0.1 and you will also see some information about the package. Most importantly, which command you can install your package with npm as a dependency to annother projects. npm install your username slash nutrition. That's it, you released your first npm package in TDD.
You have successfully completed the first release of this package. I strongly recommend that give package users a few examples at least. This is important so that they are easy to use and not have to figure out how to use it on their own.
Create the readme.md file at the root of the project. This is where we can write the package description. The first thing we need to describe is the installation process. Since this is a npm package, a single command is sufficient for installation. The md extension is the markdown markup language. This is useful because github automatically processes the file and displays the result on the project's landing page. Important, this only works if you name this file as readme.md. If you work in visual studio code like me, you can open another view beside your file where you can see the result of the processed markdown file. With this method, you can immediately correct if something does not appear as you would expect.
First, let's have an installation section. hashtag Installation. In Markdown, you can insert the source code as follows, `` `` ``. You must enter the source code between the two parts. At npmjs.com, it appears that the package can be installed in any project using the npm install command. npm i at yourusername slash nutrition. What you will still need is at least one example code that illustrates how to use the package. Add a new section named Example.
To use the capabilities of the package, you must first import the appropriate classes. There are a total of five types in the nutrition package. In the first example, we do not use error handling, so import the Food, Nutritions, and Units types. The readme shows the reader how to create a valid Food object. To do this, you first need a baseValues object, of the Nutritions type. If you accept my advice, you will never write new code in the readme file examples, because your code could easily be broken and cannot be verified at this point. It's a much better practice to copy out details from your existing spec tests that you already know are working well.
Go to the Food.spec.ts file and find the parts that are right for you. In the very first test case, there is the creation of a Food object that perfectly matches the purpose. Copy the code from the eighth to the fifteenth line. copy. paste. From this code snippet, we know from test runs that you create the Food object correctly. Show that you can query both the name and the unit of measure. Copy the getName and getUnit calls from the test. copy paste. copy paste. It is worthwhile to show in a series of comments what results these functions return in the example. returns rice. returns g.
The ability to change the amount may come. Scroll down between tests and copy the changeAmount function call. copy paste. We will write a bit longer comment here because I do not want to call each of the change functions one by one, just to let the user know that they exist. 'changes food amount value and store result in currentValues.amount there are also changeCalories, changeFat, changeCarbohydrate functions'. Give an example to query the current nutrient values. Scroll down a little between the tests and copy the call to getCurrentValues. copy paste. get current nutrition values of rice. Last but not least, give an example of basic nutrient queries. Since we did not query values in our tests in the same way as current values, we should do so now. Copy the getCurrentValues section from the readme file and rename the call to getBaseValues. While I have encouraged you not to code in the readme so far, in this case we know for sure that this little piece will work properly, so I have exceptionally modified the code here locally. copy paste. getBaseValues. Also replace the current word with base in the annotation comment. In my opinion, this example will already be a great help to the package user.
Let's give an example in the readme, which includes error handling. It is important that all examples should be able to be copied and executed by the package user, so be sure to add the imports in this case as well. I copy it from the previous example and add the error class. copy, paste. InvalidFoodAmountError. copy the baseValues and paste. Create a try catch block to create the new Food object. The reader will see from this example that the package generates an error. try catch error. Inside the try, make a new constant for the creation of the object. const rice equals new Food 'rice' Unit.GRAM and baseValues. We do nothing on the catch branch, just note in the comment that the error object type will be InvalidFoodAmountError in this case. error InvalidFoodAmountError.
Let's list the possible error types and when they can occur.
All error types. InvalidFoodAmountError -> if the amount of the baseValues is less or equal zero or
the changeAmount parameter is less or equal zero.
- EmptyFoodNameError -> if the Food first parameter is empty.
In another sentence, it is worth describing at the beginning what is the purpose of the package. First, give a name like Nutrition Calculator then a short description like Create Foods with base nutrition values, change one of the base values and recalculate the other values automatically. Change the sections title to double hastag and the package name should be the first level. With this we have created the basic readme.md file, now the users will know how to use package.
Due to this change, release a new version of the package. Note that this change will only be available to package users if you release it in a new version. As discussed earlier, you can only release a new version with a clean git repository. We now have a modification, readme.md, that needs to be committed before we can upgrade. With git status you can always see exactly what changes you have. I include all changes to staging with git add dot. After that the commit can go. git commit readme file. Finally git push to upload the change to the github server. With the help of git status, I check to see that I have no local changes. That's right, I've uploaded everything, now it's time to upgrade. I'm going to change the patch version using the npm version patch command. Version 1.0.2 was created. The last step is to publish the new version by running the npm publish command. If you see the new version at the end of the result, the publishing was successful. In this case, you should open the npmjs website and check if a new version of the package has actually been added. Log in to this page and go to packages. Find the nutrition package and click on it to go to the detailed page. The difference is spectacular, you will see the processed content of the new readme.md file with the example code immediately. If you click on the github link in the right bar, you will be taken to the package's github page where you will also see the processed readme as your homepage.
Hey, that shit! I do not believe it! You've completed the course! You can't imagine how grateful I am for your! This was my first non-free online course as an instructor. I have worked hard for the last 6 months to make this end result to the best of my knowledge. I could only be happier if you write a short review so that others can see if it is worth following the path I have outlined on the topic or not. Hope you can use the basics of TDD in your work in the future! Be good and practice a lot! Bye bye!
Have you tried the TDD method before, but failed? Don't you understand how to write the test first and then build the application logic for it? It’s not clear to you what benefits test-driven development brings you? In this course, I will lead you through the test-driven development of a real software module. You can also see the big picture and the small details.
Effective automated tests are difficult to write. You need to practice a lot to be able to apply the principles and specific implementation steps to any project. Take the course and get the funds you need to continue practicing!
Make your own Node.js package with test driven development using TypeScript tested with Jest. Through a real example, I will help you understand and learn how to build high quality modules for your projects that take advantage of TypeScript and have the least amount of bugs thanks to TDD.
In this course we will make a copy of a package from an application I made earlier. This means that you can learn the package creation method that I use on a daily basis through a code that represents true business value.
Any questions you have, feel free to contact us, I will be happy to answer!