5 lines
19 KiB
JSON
5 lines
19 KiB
JSON
{
|
|
"id": "guide/testing-services",
|
|
"title": "Testing services",
|
|
"contents": "\n\n\n<div class=\"github-links\">\n <a href=\"https://github.com/angular/angular/edit/master/aio/content/guide/testing-services.md?message=docs%3A%20describe%20your%20change...\" aria-label=\"Suggest Edits\" title=\"Suggest Edits\"><i class=\"material-icons\" aria-hidden=\"true\" role=\"img\">mode_edit</i></a>\n</div>\n\n\n<div class=\"content\">\n <h1 id=\"testing-services\">Testing services<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/testing-services#testing-services\"><i class=\"material-icons\">link</i></a></h1>\n<p>To check that your services are working as you intend, you can write tests specifically for them.</p>\n<div class=\"alert is-helpful\">\n<p> For the sample app that the testing guides describe, see the <live-example name=\"testing\" embedded-style=\"\" nodownload=\"\">sample app</live-example>.</p>\n<p> For the tests features in the testing guides, see <live-example name=\"testing\" stackblitz=\"specs\" nodownload=\"\">tests</live-example>.</p>\n</div>\n<p>Services are often the easiest files to unit test.\nHere are some synchronous and asynchronous unit tests of the <code>ValueService</code>\nwritten without assistance from Angular testing utilities.</p>\n<code-example path=\"testing/src/app/demo/demo.spec.ts\" region=\"ValueService\" header=\"app/demo/demo.spec.ts\">\n// Straight Jasmine testing without Angular's testing support\ndescribe('ValueService', () => {\n let service: ValueService;\n beforeEach(() => { service = new ValueService(); });\n\n it('#getValue should return real value', () => {\n expect(service.getValue()).toBe('real value');\n });\n\n it('#getObservableValue should return value from observable',\n (done: DoneFn) => {\n service.getObservableValue().subscribe(value => {\n expect(value).toBe('observable value');\n done();\n });\n });\n\n it('#getPromiseValue should return value from a promise',\n (done: DoneFn) => {\n service.getPromiseValue().then(value => {\n expect(value).toBe('promise value');\n done();\n });\n });\n});\n\n</code-example>\n<a id=\"services-with-dependencies\"></a>\n<h2 id=\"services-with-dependencies\">Services with dependencies<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/testing-services#services-with-dependencies\"><i class=\"material-icons\">link</i></a></h2>\n<p>Services often depend on other services that Angular injects into the constructor.\nIn many cases, it's easy to create and <em>inject</em> these dependencies by hand while\ncalling the service's constructor.</p>\n<p>The <code>MasterService</code> is a simple example:</p>\n<code-example path=\"testing/src/app/demo/demo.ts\" region=\"MasterService\" header=\"app/demo/demo.ts\">\n@<a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a>()\nexport class MasterService {\n constructor(private valueService: ValueService) { }\n getValue() { return this.valueService.getValue(); }\n}\n\n</code-example>\n<p><code>MasterService</code> delegates its only method, <code>getValue</code>, to the injected <code>ValueService</code>.</p>\n<p>Here are several ways to test it.</p>\n<code-example path=\"testing/src/app/demo/demo.spec.ts\" region=\"MasterService\" header=\"app/demo/demo.spec.ts\">\ndescribe('MasterService without Angular testing support', () => {\n let masterService: MasterService;\n\n it('#getValue should return real value from the real service', () => {\n masterService = new MasterService(new ValueService());\n expect(masterService.getValue()).toBe('real value');\n });\n\n it('#getValue should return faked value from a fakeService', () => {\n masterService = new MasterService(new FakeValueService());\n expect(masterService.getValue()).toBe('faked service value');\n });\n\n it('#getValue should return faked value from a fake object', () => {\n const fake = { getValue: () => 'fake value' };\n masterService = new MasterService(fake as ValueService);\n expect(masterService.getValue()).toBe('fake value');\n });\n\n it('#getValue should return stubbed value from a spy', () => {\n // create `getValue` spy on an object representing the ValueService\n const valueServiceSpy =\n jasmine.createSpyObj('ValueService', ['getValue']);\n\n // set the value to return when the `getValue` spy is called.\n const stubValue = 'stub value';\n valueServiceSpy.getValue.and.returnValue(stubValue);\n\n masterService = new MasterService(valueServiceSpy);\n\n expect(masterService.getValue())\n .toBe(stubValue, 'service returned stub value');\n expect(valueServiceSpy.getValue.calls.count())\n .toBe(1, 'spy method was called once');\n expect(valueServiceSpy.getValue.calls.mostRecent().returnValue)\n .toBe(stubValue);\n });\n});\n\n</code-example>\n<p>The first test creates a <code>ValueService</code> with <code>new</code> and passes it to the <code>MasterService</code> constructor.</p>\n<p>However, injecting the real service rarely works well as most dependent services are difficult to create and control.</p>\n<p>Instead you can mock the dependency, use a dummy value, or create a\n<a href=\"https://jasmine.github.io/2.0/introduction.html#section-Spies\">spy</a>\non the pertinent service method.</p>\n<div class=\"alert is-helpful\">\n<p>Prefer spies as they are usually the easiest way to mock services.</p>\n</div>\n<p>These standard testing techniques are great for unit testing services in isolation.</p>\n<p>However, you almost always inject services into application classes using Angular\ndependency injection and you should have tests that reflect that usage pattern.\nAngular testing utilities make it easy to investigate how injected services behave.</p>\n<h2 id=\"testing-services-with-the-testbed\">Testing services with the <em>TestBed</em><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/testing-services#testing-services-with-the-testbed\"><i class=\"material-icons\">link</i></a></h2>\n<p>Your app relies on Angular <a href=\"guide/dependency-injection\">dependency injection (DI)</a>\nto create services.\nWhen a service has a dependent service, DI finds or creates that dependent service.\nAnd if that dependent service has its own dependencies, DI finds-or-creates them as well.</p>\n<p>As service <em>consumer</em>, you don't worry about any of this.\nYou don't worry about the order of constructor arguments or how they're created.</p>\n<p>As a service <em>tester</em>, you must at least think about the first level of service dependencies\nbut you <em>can</em> let Angular DI do the service creation and deal with constructor argument order\nwhen you use the <code><a href=\"api/core/testing/TestBed\" class=\"code-anchor\">TestBed</a></code> testing utility to provide and create services.</p>\n<a id=\"testbed\"></a>\n<h2 id=\"angular-testbed\">Angular <em>TestBed</em><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/testing-services#angular-testbed\"><i class=\"material-icons\">link</i></a></h2>\n<p>The <code><a href=\"api/core/testing/TestBed\" class=\"code-anchor\">TestBed</a></code> is the most important of the Angular testing utilities.\nThe <code><a href=\"api/core/testing/TestBed\" class=\"code-anchor\">TestBed</a></code> creates a dynamically-constructed Angular <em>test</em> module that emulates\nan Angular <a href=\"guide/ngmodules\">@NgModule</a>.</p>\n<p>The <code>TestBed.configureTestingModule()</code> method takes a metadata object that can have most of the properties of an <a href=\"guide/ngmodules\">@NgModule</a>.</p>\n<p>To test a service, you set the <code>providers</code> metadata property with an\narray of the services that you'll test or mock.</p>\n<code-example path=\"testing/src/app/demo/demo.testbed.spec.ts\" region=\"value-service-before-each\" header=\"app/demo/demo.testbed.spec.ts (provide ValueService in beforeEach)\">\nlet service: ValueService;\n\nbeforeEach(() => {\n TestBed.configureTestingModule({ providers: [ValueService] });\n});\n\n</code-example>\n<p>Then inject it inside a test by calling <code>TestBed.inject()</code> with the service class as the argument.</p>\n<div class=\"alert is-helpful\">\n<p><strong>Note:</strong> <code>TestBed.get()</code> was deprecated as of Angular version 9.\nTo help minimize breaking changes, Angular introduces a new function called <code>TestBed.inject()</code>, which you should use instead.\nFor information on the removal of <code>TestBed.get()</code>,\nsee its entry in the <a href=\"guide/deprecations#index\">Deprecations index</a>.</p>\n</div>\n<code-example path=\"testing/src/app/demo/demo.testbed.spec.ts\" region=\"value-service-inject-it\">\nit('should use ValueService', () => {\n service = TestBed.inject(ValueService);\n expect(service.getValue()).toBe('real value');\n});\n\n</code-example>\n<p>Or inside the <code>beforeEach()</code> if you prefer to inject the service as part of your setup.</p>\n<code-example path=\"testing/src/app/demo/demo.testbed.spec.ts\" region=\"value-service-inject-before-each\">\nbeforeEach(() => {\n TestBed.configureTestingModule({ providers: [ValueService] });\n service = TestBed.inject(ValueService);\n});\n\n</code-example>\n<p>When testing a service with a dependency, provide the mock in the <code>providers</code> array.</p>\n<p>In the following example, the mock is a spy object.</p>\n<code-example path=\"testing/src/app/demo/demo.testbed.spec.ts\" region=\"master-service-before-each\">\nlet masterService: MasterService;\nlet valueServiceSpy: jasmine.SpyObj<ValueService>;\n\nbeforeEach(() => {\n const spy = jasmine.createSpyObj('ValueService', ['getValue']);\n\n TestBed.configureTestingModule({\n // Provide both the service-to-test and its (spy) dependency\n providers: [\n MasterService,\n { provide: ValueService, useValue: spy }\n ]\n });\n // <a href=\"api/core/Inject\" class=\"code-anchor\">Inject</a> both the service-to-test and its (spy) dependency\n masterService = TestBed.inject(MasterService);\n valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>;\n});\n\n</code-example>\n<p>The test consumes that spy in the same way it did earlier.</p>\n<code-example path=\"testing/src/app/demo/demo.testbed.spec.ts\" region=\"master-service-it\">\nit('#getValue should return stubbed value from a spy', () => {\n const stubValue = 'stub value';\n valueServiceSpy.getValue.and.returnValue(stubValue);\n\n expect(masterService.getValue())\n .toBe(stubValue, 'service returned stub value');\n expect(valueServiceSpy.getValue.calls.count())\n .toBe(1, 'spy method was called once');\n expect(valueServiceSpy.getValue.calls.mostRecent().returnValue)\n .toBe(stubValue);\n});\n\n</code-example>\n<a id=\"no-before-each\"></a>\n<h2 id=\"testing-without-beforeeach\">Testing without <em>beforeEach()</em><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/testing-services#testing-without-beforeeach\"><i class=\"material-icons\">link</i></a></h2>\n<p>Most test suites in this guide call <code>beforeEach()</code> to set the preconditions for each <code>it()</code> test\nand rely on the <code><a href=\"api/core/testing/TestBed\" class=\"code-anchor\">TestBed</a></code> to create classes and inject services.</p>\n<p>There's another school of testing that never calls <code>beforeEach()</code> and prefers to create classes explicitly rather than use the <code><a href=\"api/core/testing/TestBed\" class=\"code-anchor\">TestBed</a></code>.</p>\n<p>Here's how you might rewrite one of the <code>MasterService</code> tests in that style.</p>\n<p>Begin by putting re-usable, preparatory code in a <em>setup</em> function instead of <code>beforeEach()</code>.</p>\n<code-example path=\"testing/src/app/demo/demo.spec.ts\" region=\"no-before-each-setup\" header=\"app/demo/demo.spec.ts (setup)\">\nfunction setup() {\n const valueServiceSpy =\n jasmine.createSpyObj('ValueService', ['getValue']);\n const stubValue = 'stub value';\n const masterService = new MasterService(valueServiceSpy);\n\n valueServiceSpy.getValue.and.returnValue(stubValue);\n return { masterService, stubValue, valueServiceSpy };\n}\n\n</code-example>\n<p>The <code>setup()</code> function returns an object literal\nwith the variables, such as <code>masterService</code>, that a test might reference.\nYou don't define <em>semi-global</em> variables (e.g., <code>let masterService: MasterService</code>)\nin the body of the <code>describe()</code>.</p>\n<p>Then each test invokes <code>setup()</code> in its first line, before continuing\nwith steps that manipulate the test subject and assert expectations.</p>\n<code-example path=\"testing/src/app/demo/demo.spec.ts\" region=\"no-before-each-test\">\nit('#getValue should return stubbed value from a spy', () => {\n const { masterService, stubValue, valueServiceSpy } = setup();\n expect(masterService.getValue())\n .toBe(stubValue, 'service returned stub value');\n expect(valueServiceSpy.getValue.calls.count())\n .toBe(1, 'spy method was called once');\n expect(valueServiceSpy.getValue.calls.mostRecent().returnValue)\n .toBe(stubValue);\n});\n\n</code-example>\n<p>Notice how the test uses\n<a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment\"><em>destructuring assignment</em></a>\nto extract the setup variables that it needs.</p>\n<code-example path=\"testing/src/app/demo/demo.spec.ts\" region=\"no-before-each-setup-call\">\nconst { masterService, stubValue, valueServiceSpy } = setup();\n\n</code-example>\n<p>Many developers feel this approach is cleaner and more explicit than the\ntraditional <code>beforeEach()</code> style.</p>\n<p>Although this testing guide follows the traditional style and\nthe default <a href=\"https://github.com/angular/angular-cli\">CLI schematics</a>\ngenerate test files with <code>beforeEach()</code> and <code><a href=\"api/core/testing/TestBed\" class=\"code-anchor\">TestBed</a></code>,\nfeel free to adopt <em>this alternative approach</em> in your own projects.</p>\n<h2 id=\"testing-http-services\">Testing HTTP services<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/testing-services#testing-http-services\"><i class=\"material-icons\">link</i></a></h2>\n<p>Data services that make HTTP calls to remote servers typically inject and delegate\nto the Angular <a href=\"guide/http\"><code>HttpClient</code></a> service for XHR calls.</p>\n<p>You can test a data service with an injected <code><a href=\"api/common/http/HttpClient\" class=\"code-anchor\">HttpClient</a></code> spy as you would\ntest any service with a dependency.\n<code-example path=\"testing/src/app/model/hero.service.spec.ts\" region=\"test-with-spies\" header=\"app/model/hero.service.spec.ts (tests with spies)\">\nlet httpClientSpy: { get: jasmine.Spy };\nlet heroService: HeroService;\n\nbeforeEach(() => {\n // TODO: spy on other methods too\n httpClientSpy = jasmine.createSpyObj('<a href=\"api/common/http/HttpClient\" class=\"code-anchor\">HttpClient</a>', ['get']);\n heroService = new HeroService(httpClientSpy as any);\n});\n\nit('should return expected heroes (<a href=\"api/common/http/HttpClient\" class=\"code-anchor\">HttpClient</a> called once)', () => {\n const expectedHeroes: Hero[] =\n [{ id: 1, name: 'A' }, { id: 2, name: 'B' }];\n\n httpClientSpy.get.and.returnValue(asyncData(expectedHeroes));\n\n heroService.getHeroes().subscribe(\n heroes => expect(heroes).toEqual(expectedHeroes, 'expected heroes'),\n fail\n );\n expect(httpClientSpy.get.calls.count()).toBe(1, 'one call');\n});\n\nit('should return an error when the server returns a 404', () => {\n const errorResponse = new <a href=\"api/common/http/HttpErrorResponse\" class=\"code-anchor\">HttpErrorResponse</a>({\n error: 'test 404 error',\n status: 404, statusText: 'Not Found'\n });\n\n httpClientSpy.get.and.returnValue(asyncError(errorResponse));\n\n heroService.getHeroes().subscribe(\n heroes => fail('expected an error, not heroes'),\n error => expect(error.message).toContain('test 404 error')\n );\n});\n\n</code-example></p>\n<div class=\"alert is-important\">\n<p>The <code>HeroService</code> methods return <code>Observables</code>. You must\n<em>subscribe</em> to an observable to (a) cause it to execute and (b)\nassert that the method succeeds or fails.</p>\n<p>The <code>subscribe()</code> method takes a success (<code>next</code>) and fail (<code>error</code>) callback.\nMake sure you provide <em>both</em> callbacks so that you capture errors.\nNeglecting to do so produces an asynchronous uncaught observable error that\nthe test runner will likely attribute to a completely different test.</p>\n</div>\n<h2 id=\"httpclienttestingmodule\"><em>HttpClientTestingModule</em><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/testing-services#httpclienttestingmodule\"><i class=\"material-icons\">link</i></a></h2>\n<p>Extended interactions between a data service and the <code><a href=\"api/common/http/HttpClient\" class=\"code-anchor\">HttpClient</a></code> can be complex\nand difficult to mock with spies.</p>\n<p>The <code><a href=\"api/common/http/testing/HttpClientTestingModule\" class=\"code-anchor\">HttpClientTestingModule</a></code> can make these testing scenarios more manageable.</p>\n<p>While the <em>code sample</em> accompanying this guide demonstrates <code><a href=\"api/common/http/testing/HttpClientTestingModule\" class=\"code-anchor\">HttpClientTestingModule</a></code>,\nthis page defers to the <a href=\"guide/http#testing-http-requests\">Http guide</a>,\nwhich covers testing with the <code><a href=\"api/common/http/testing/HttpClientTestingModule\" class=\"code-anchor\">HttpClientTestingModule</a></code> in detail.</p>\n\n \n</div>\n\n<!-- links to this doc:\n - guide/testing\n - guide/testing-components-basics\n-->\n<!-- links from this doc:\n - api/common/http/HttpClient\n - api/common/http/HttpErrorResponse\n - api/common/http/testing/HttpClientTestingModule\n - api/core/Inject\n - api/core/Injectable\n - api/core/testing/TestBed\n - guide/dependency-injection\n - guide/deprecations#index\n - guide/http\n - guide/http#testing-http-requests\n - guide/ngmodules\n - guide/testing-services#angular-testbed\n - guide/testing-services#httpclienttestingmodule\n - guide/testing-services#services-with-dependencies\n - guide/testing-services#testing-http-services\n - guide/testing-services#testing-services\n - guide/testing-services#testing-services-with-the-testbed\n - guide/testing-services#testing-without-beforeeach\n - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment\n - https://github.com/angular/angular-cli\n - https://github.com/angular/angular/edit/master/aio/content/guide/testing-services.md?message=docs%3A%20describe%20your%20change...\n - https://jasmine.github.io/2.0/introduction.html#section-Spies\n-->"
|
|
} |