Last active
November 22, 2017 14:30
-
-
Save Janneman96/e0a1f19d87536ca60b03d9a0017da370 to your computer and use it in GitHub Desktop.
How to unit test in Angular TypeScript using Jasmine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {BlogFeedComponent} from './blog-feed.component'; | |
import {CoreApiService} from "../../../services/api/api.service"; | |
import {IBlogFeed} from "./blog-feed.component.interface"; | |
import createSpy = jasmine.createSpy; | |
import createSpyObj = jasmine.createSpyObj; | |
/** | |
* In the describe function, we describe what we are testing. In this case, we are testing the BlogFeedComponent. | |
* | |
* We can run all tests by running "ng test" in the terminal at the root directory of this project. | |
*/ | |
describe('BlogFeedComponent', () => { | |
/** | |
* Even though providerApi and blogFeed are actually spies(also known as mocks), we do not call them providerApiSpy | |
* and blogFeedSpy. The usage of the spies is the same as a real object. To prevent confusion, we don't type spy | |
* behind the variable names. | |
* | |
* You can find more information about spies in the next comment section. | |
*/ | |
let providerApi; | |
let blogFeed; | |
let blogFeedComponent: BlogFeedComponent; | |
/** | |
* The beforeEach() function is executed before every unit test. The beforeEach is typically used to initialize | |
* variables that are used in every unit test. | |
* | |
* ## Spy objects ## | |
* In this example, we use Spy objects. Spy objects are objects that mock the behaviour of real objects. | |
* This means that we can test a component, without testing the dependencies of the component. Instead of using the | |
* actual dependencies, we mock that dependency and use a fake object instead. | |
* | |
* ## Creating a spy object ## | |
* providerApi = createSpyObj('CoreApiService', ["query"]); creates an object of type CoreApiService. | |
* The fake object has got one function: query(). Now we can call the query function on this object as such: | |
* providerApi.query(). At the moment the function doesn't return anything. We only defined that there is a function | |
* called "query", but we didn't define what the function does. We will do that later. | |
* | |
* We create the blogFeed object the same way as we create the providerApi object. The blogFeed object has got | |
* Observable as type and has got a subscribe() function. We didn't implement the subscribe() function in | |
* this case, because we don't need the implementation of the subscribe() function in any of the tests in this file. | |
* We do need the declaration of the function, so we can call it. | |
* | |
* providerApi.query.and.returnValue(blogFeed);. In this line, we access the providerApi object and give the | |
* function "query" an implementation. From now on, the query() function in the providerApi object returns the | |
* blogFeed object. | |
* | |
* Finally we create the BlogFeedComponent and pass the fake providerApi object in its parameter. This is the | |
* object we are testing. | |
*/ | |
beforeEach(function () { | |
// Arrange | |
providerApi = createSpyObj('CoreApiService', ['query']); | |
blogFeed = createSpyObj('Observable', ['subscribe']); | |
providerApi.query.and.returnValue(blogFeed); | |
blogFeedComponent = new BlogFeedComponent(providerApi); | |
}); | |
/** | |
* The it() function is the actual unit test and it will show in the test results as a success or as a failed. | |
* | |
* The it function should read like a sentence: "It should be created". | |
* | |
* Usually we split a unit test in three parts: Arrange, Act and Assert. In arrange, we assign values to variables. In | |
* act we use the functionality we want to test. In assert, we check if the functionality does what we expect it to | |
* do. Sometimes we don't have all three parts in the function. In this case, the arrange has already been done in | |
* the beforeEach() function above. | |
* | |
* In this unit test, we check if the blogFeedComponent is defined. The test succeeds if the blogFeedComponent is | |
* defined and fails if it is undefined. It is usually a good idea to start with the simplest test we can think of | |
* and build up the complexity in further unit tests. | |
*/ | |
it('can be created', () => { | |
// Assert | |
expect(blogFeedComponent).toBeDefined(); | |
expect(blogFeedComponent).not.toBeNull(); | |
}); | |
/** | |
* The BlogFeedComponent has got a getRecentPosts() function that returns the most recent blog posts. In this test | |
* we check if the length of the blog post list the function returns is exactly as long as we expect it to be. | |
* | |
* ## The three A's: Arrange, Act, Assert ## | |
* In this unit test, we have a great example of the three A's: Arrange, Act and Assert. "Follow the "3As" pattern | |
* for unit tests: Arrange, Act, Assert. Specifically, use separate code paragraphs (groups of lines of code | |
* separated by a blank line) for each of the As. Arrange is variable declaration and initialization. Act is | |
* invoking the code under test. Assert is using the expect() functions to verify that expectations were met. | |
* Following this pattern consistently makes it easy to revisit test code."(source: http://bit.ly/2ujbsKt) | |
* | |
* For more information about the general idea of the three A's, i refer to this link: | |
* http://defragdev.com/blog/?p=783 | |
* | |
* ## Arrange ## | |
* We initialise a fake/hardcoded blog feed. The initialising progress is not important for reading this test. | |
* Therefore we moved the functionality of creating the hardcoded blog feed to a separate function: | |
* provideDummyBlogFeed(). Next, we set the "blogs" property in the BlogFeedComponent to be the fake blog feed. | |
* | |
* ## Act ## | |
* We run the functionality we want to test and put the output in a variable named "recentPosts". | |
* | |
* ## Assert ## | |
* We expect the length of the recent posts array to be exactly 3 items long. We always want to put the actual value | |
* in the "expect" parameter and the expected value in the "toBe" parameter: expect(actualValue).toBe(expectedValue) | |
*/ | |
it('can give three blogs when asking the most recent blogs', () => { | |
// Arrange | |
let dummyBlogFeed = provideDummyBlogFeed(6); | |
blogFeedComponent.blogs = dummyBlogFeed; | |
// Act | |
let recentPosts = blogFeedComponent.getRecentPosts(); | |
// Assert | |
expect(recentPosts.length).toBe(3); | |
}); | |
/** | |
* It is a good practice to test a function with multiple inputs. The function getRecentPosts gives the four newest | |
* posts in the above test where there are six posts in total. To make sure it keeps working with less than three | |
* posts, we create a test case where there is only two blogs. Here we see that it keeps working with two blogs and | |
* just returns the two. | |
*/ | |
it('gives less than 3 blogs when asking the most recent blogs when there is less than 3 blogs available', () => { | |
// Arrange | |
blogFeedComponent.blogs = provideDummyBlogFeed(2); | |
// Act | |
let recentPosts = blogFeedComponent.getRecentPosts(); | |
// Assert | |
expect(recentPosts.length).toBe(2); | |
}); | |
/** | |
* Values like 0, null, [] and '' are some edge case values that could cause some errors in code when these edge | |
* cases are overlooked. When one of these values is a normal input or output value, it is a good idea to write a | |
* test, checking that case. In this test we see that recent posts keeps working when there are no blogs. We get an | |
* empty list, which is expected behaviour. | |
*/ | |
it('gives an empty list when there are no recent posts', () => { | |
// Arrange | |
blogFeedComponent.blogs = provideDummyBlogFeed(0); | |
// Act | |
let recentPosts = blogFeedComponent.getRecentPosts(); | |
// Assert | |
expect(recentPosts.length).toBe(0); | |
}); | |
/** | |
* In this test we want to check if the getFeed() function in blogFeedComponent is called when we call the | |
* ngOnInit() function. | |
* | |
* The blogFeedComponent object is an actual object, we didn't make it a fake object like the providerApi. That | |
* means we don't have to define fake functions on the object, because it already has the functions. | |
* | |
* ## Arrange ## | |
* Because we only want to check if the function gets called, but don't want to overwrite the function, we can spy | |
* on the function of the object. We do that by typing: spyOn(blogFeedComponent, 'getFeed');. Here, 'getFeed' is the | |
* name of the function we are spying on. | |
* | |
* ## Act ## | |
* We call the ngOnInit() function. We don't have to put the result in a variable, because we are not testing what | |
* the function returns in this unit test. | |
* | |
* ## Assert ## | |
* In this test, we expect that the getFeed() function of the blogFeedComponent is called in the ngOnInit() | |
* function. | |
*/ | |
it('can get the blog feed when ngOnInit is called', () => { | |
// Arrange | |
spyOn(blogFeedComponent, 'getFeed'); | |
// Act | |
blogFeedComponent.ngOnInit(); | |
// Assert | |
expect(blogFeedComponent.getFeed).toHaveBeenCalled(); | |
}); | |
/** | |
* ## Testing private functions ## | |
* Private functions are great and we don't want to make them public just to test them. | |
* Typescript doesn't allow us to access private functions by default. However, typescript compiles to javascript | |
* and javascript does allow us to access private functions. Therefore, if we can trespass the compiler and call the | |
* function anyways, we can test the private function without making it a public function. | |
* | |
* We can access the private function by casting the component to any as shown: | |
* (blogFeedComponent as any) | |
* | |
* Casting the component to any will prevent the IDE from auto completing the function you are testing, so just copy | |
* the function name from the component you are testing to prevent typo's. | |
*/ | |
it('can provide mapped data', () => { | |
// Arrange | |
let testTitle = "this is the title"; | |
let testBody = "this is the body"; | |
let testModifiedBy = "this is modified by"; | |
let testModifiedDate = "it is modified on"; | |
let testData = [{ | |
"Ext.Blg.Tit": testTitle, | |
"Ext.Blg.Bod": testBody, | |
"Ext.Blg.Bod!ModifiedBy": testModifiedBy, | |
"Ext.Blg.Bod!ModifiedDate": testModifiedDate, | |
}]; | |
let expectedResult = [{ | |
title: testTitle, | |
body: testBody, | |
modifiedBy: testModifiedBy, | |
modifiedDate: testModifiedDate | |
}]; | |
// Act | |
(blogFeedComponent as any).provideBlogs(testData); | |
// Assert | |
expect(blogFeedComponent.blogs as any).toEqual(expectedResult); | |
}); | |
}); | |
/** | |
* This function provides a list of empty IBlogFeed mocks/spies. | |
* | |
* This function is used by unit test 'it can give three blogs when asking the most recent blogs'. This list of 5 items | |
* is required to split it into a list of 3 items. | |
* | |
* We decided to put this functionality in a seperate function, because the functionality is not interesting in the | |
* test. In the function name, we described exactly what this function does. So when you use this function in a test, | |
* you have a general idea of what the function does, so you don't have to read the actual implementation of the | |
* function. | |
* | |
* @returns {IBlogFeed[]} Array of empty IBlogFeed implementations | |
*/ | |
function provideDummyBlogFeed(length: Number): IBlogFeed[] { | |
let blogs = []; | |
for (let i = 0; i < length; i++) { | |
blogs.push(createSpy('IBlogFeed')); | |
} | |
return blogs; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment