docs(testing): explain rationale for stubbed routerLink tests (#2422)

This commit is contained in:
Ward Bell 2016-09-20 13:45:50 -07:00 committed by GitHub
parent 0f99a7c130
commit 55bf126489
1 changed files with 82 additions and 65 deletions

View File

@ -11,7 +11,7 @@ block includes
Along the way you will learn some general testing principles and techniques but the focus is on Along the way you will learn some general testing principles and techniques but the focus is on
Angular testing. Angular testing.
a#top #top
:marked :marked
# Table of Contents # Table of Contents
1. [Introduction to Angular Testing](#testing-intro) 1. [Introduction to Angular Testing](#testing-intro)
@ -83,7 +83,7 @@ a#top
a(href="#top").to-top Back to top a(href="#top").to-top Back to top
.l-hr .l-hr
a#testing-intro #testing-intro
:marked :marked
# Introduction to Angular Testing # Introduction to Angular Testing
@ -154,7 +154,7 @@ table(width="100%")
and assert that the application responds in the browser as expected. and assert that the application responds in the browser as expected.
.l-hr .l-hr
a#setup #setup
:marked :marked
# Setup # Setup
@ -184,7 +184,7 @@ a#setup
you can skip the rest of this section and get on with your first test. you can skip the rest of this section and get on with your first test.
The QuickStart repo provides all necessary setup. The QuickStart repo provides all necessary setup.
a#setup-files #setup-files
:marked :marked
### Setup files ### Setup files
Here's brief description of the setup files. Here's brief description of the setup files.
@ -247,7 +247,7 @@ table(width="100%")
They were installed when you ran `npm install`. They were installed when you ran `npm install`.
.l-hr .l-hr
a#1st-karma-test #1st-karma-test
:marked :marked
# The first karma test # The first karma test
@ -368,7 +368,7 @@ figure.image-display
a(href="#top").to-top Back to top a(href="#top").to-top Back to top
.l-hr .l-hr
a#atp-intro #atp-intro
:marked :marked
# The Angular Testing Platform (ATP) # The Angular Testing Platform (ATP)
@ -439,7 +439,7 @@ a(href="#top").to-top Back to top
.l-hr .l-hr
a#sample-app #sample-app
:marked :marked
# The sample application and its tests # The sample application and its tests
@ -460,7 +460,7 @@ a#sample-app
a(href="#top").to-top Back to top a(href="#top").to-top Back to top
.l-hr .l-hr
a#simple-component-test #simple-component-test
:marked :marked
# Test a component # Test a component
@ -476,7 +476,7 @@ a#simple-component-test
Start with ES6 import statements to get access to symbols referenced in the spec. Start with ES6 import statements to get access to symbols referenced in the spec.
+makeExample('testing/ts/app/banner.component.spec.ts', 'imports', 'app/banner.component.spec.ts (imports)')(format='.') +makeExample('testing/ts/app/banner.component.spec.ts', 'imports', 'app/banner.component.spec.ts (imports)')(format='.')
a#configure-testing-module #configure-testing-module
:marked :marked
Here's the setup for the tests followed by observations about the `beforeEach`: Here's the setup for the tests followed by observations about the `beforeEach`:
+makeExample('testing/ts/app/banner.component.spec.ts', 'setup', 'app/banner.component.spec.ts (setup)')(format='.') +makeExample('testing/ts/app/banner.component.spec.ts', 'setup', 'app/banner.component.spec.ts (setup)')(format='.')
@ -492,7 +492,7 @@ a#configure-testing-module
But that would lead to tons more configuration in order to support the other components within `AppModule` But that would lead to tons more configuration in order to support the other components within `AppModule`
that have nothing to do with `BannerComponent`. that have nothing to do with `BannerComponent`.
a#create-component #create-component
:marked :marked
`TestBed.createComponent` creates an instance of `BannerComponent` to test. `TestBed.createComponent` creates an instance of `BannerComponent` to test.
The method returns a `ComponentFixture`, a handle on the test environment surrounding the created component. The method returns a `ComponentFixture`, a handle on the test environment surrounding the created component.
@ -516,7 +516,7 @@ a#create-component
:markdown :markdown
These tests ask the `DebugElement` for the native HTML element to satisfy their expectations. These tests ask the `DebugElement` for the native HTML element to satisfy their expectations.
a#detect-changes #detect-changes
:marked :marked
### _detectChanges_: Angular change detection under test ### _detectChanges_: Angular change detection under test
@ -540,7 +540,7 @@ a#detect-changes
It gives the tester an opportunity to investigate the state of It gives the tester an opportunity to investigate the state of
the component _before Angular initiates data binding or calls lifecycle hooks_. the component _before Angular initiates data binding or calls lifecycle hooks_.
a#auto-detect-changes #auto-detect-changes
:marked :marked
### Automatic change detection ### Automatic change detection
Some testers prefer that the Angular test environment run change detection automatically. Some testers prefer that the Angular test environment run change detection automatically.
@ -570,7 +570,7 @@ a(href="#top").to-top Back to top
.l-hr .l-hr
a#component-with-dependency #component-with-dependency
:marked :marked
# Test a component with a dependency # Test a component with a dependency
Components often have service dependencies. Components often have service dependencies.
@ -605,7 +605,7 @@ a#component-with-dependency
and its tests: and its tests:
+makeExample('testing/ts/app/welcome.component.spec.ts', 'user-service-stub')(format='.') +makeExample('testing/ts/app/welcome.component.spec.ts', 'user-service-stub')(format='.')
a#injected-service-reference #injected-service-reference
:marked :marked
## Referencing injected services ## Referencing injected services
The tests need access to the injected (stubbed) `UserService`. The tests need access to the injected (stubbed) `UserService`.
@ -641,7 +641,7 @@ a#injected-service-reference
.alert.is-important .alert.is-important
:marked :marked
Use the component's own injector to get the component's injected service. Use the component's own injector to get the component's injected service.
a#welcome-spec-setup #welcome-spec-setup
:marked :marked
Here's the complete, preferred `beforeEach`: Here's the complete, preferred `beforeEach`:
+makeExample('testing/ts/app/welcome.component.spec.ts', 'setup', 'app/welcome.component.spec.ts')(format='.') +makeExample('testing/ts/app/welcome.component.spec.ts', 'setup', 'app/welcome.component.spec.ts')(format='.')
@ -658,7 +658,7 @@ a(href="#top").to-top Back to top
.l-hr .l-hr
a#component-with-async-service #component-with-async-service
:marked :marked
# Test a component with an async service # Test a component with an async service
Many services return values asynchronously. Many services return values asynchronously.
@ -677,7 +677,7 @@ a#component-with-async-service
They should emulate such calls. The setup in this `app/shared/twain.component.spec.ts` shows one way to do that: They should emulate such calls. The setup in this `app/shared/twain.component.spec.ts` shows one way to do that:
+makeExample('testing/ts/app/shared/twain.component.spec.ts', 'setup', 'app/shared/twain.component.spec.ts (setup)')(format='.') +makeExample('testing/ts/app/shared/twain.component.spec.ts', 'setup', 'app/shared/twain.component.spec.ts (setup)')(format='.')
a#service-spy #service-spy
:marked :marked
### Spying on the real service ### Spying on the real service
@ -709,7 +709,7 @@ a#service-spy
The test must become an "async test" ... like the third test The test must become an "async test" ... like the third test
a#async #async
:marked :marked
## The _async_ function in _it_ ## The _async_ function in _it_
@ -731,7 +731,7 @@ a#async
Some functions called within a test (such as `fixture.whenStable`) continue to reveal their asynchronous behavior. Some functions called within a test (such as `fixture.whenStable`) continue to reveal their asynchronous behavior.
Consider also the [_fakeAsync_](#fake-async) alternative which affords a more linear coding experience. Consider also the [_fakeAsync_](#fake-async) alternative which affords a more linear coding experience.
a#when-stable #when-stable
:marked :marked
## _whenStable_ ## _whenStable_
The test must wait for the `getQuote` promise to resolve. The test must wait for the `getQuote` promise to resolve.
@ -754,8 +754,8 @@ a#when-stable
The test kicks off another round of change detection (`fixture.detechChanges`) which tells Angular to update the DOM with the quote. The test kicks off another round of change detection (`fixture.detechChanges`) which tells Angular to update the DOM with the quote.
The `getQuote` helper method extracts the display element text and the expectation confirms that the text matches the test quote. The `getQuote` helper method extracts the display element text and the expectation confirms that the text matches the test quote.
a#fakeAsync #fakeAsync
a#fake-async #fake-async
:marked :marked
## The _fakeAsync_ function ## The _fakeAsync_ function
@ -776,8 +776,8 @@ a#fake-async
There are limitations. For example, you cannot make an XHR call from within a `fakeAsync`. There are limitations. For example, you cannot make an XHR call from within a `fakeAsync`.
a#tick #tick
a#tick-first-look #tick-first-look
:marked :marked
## The _tick_ function ## The _tick_ function
Compare the third and fourth tests. Notice that `fixture.whenStable` is gone, replaced by `tick()`. Compare the third and fourth tests. Notice that `fixture.whenStable` is gone, replaced by `tick()`.
@ -795,7 +795,7 @@ a#tick-first-look
To more fully appreciate the improvement, imagine a succession of asynchronous operations, To more fully appreciate the improvement, imagine a succession of asynchronous operations,
chained in a long sequence of promise callbacks. chained in a long sequence of promise callbacks.
a#jasmine-done #jasmine-done
:marked :marked
## _jasmine.done_ ## _jasmine.done_
@ -819,7 +819,7 @@ a(href="#top").to-top Back to top
.l-hr .l-hr
a#component-with-external-template #component-with-external-template
:marked :marked
# Test a component with an external template # Test a component with an external template
The `TestBed.createComponent` is a synchronous method. The `TestBed.createComponent` is a synchronous method.
@ -847,7 +847,7 @@ a#component-with-external-template
The `app/dashboard/dashboard-hero.component.spec.ts` demonstrates the pre-compilation process: The `app/dashboard/dashboard-hero.component.spec.ts` demonstrates the pre-compilation process:
+makeExample('testing/ts/app/dashboard/dashboard-hero.component.spec.ts', 'compile-components', 'app/dashboard/dashboard-hero.component.spec.ts (compileComponents)')(format='.') +makeExample('testing/ts/app/dashboard/dashboard-hero.component.spec.ts', 'compile-components', 'app/dashboard/dashboard-hero.component.spec.ts (compileComponents)')(format='.')
a#async-in-before-each #async-in-before-each
:marked :marked
## The _async_ function in _beforeEach_ ## The _async_ function in _beforeEach_
@ -855,7 +855,7 @@ a#async-in-before-each
The `async` function arranges for the tester's code to run in a special _async test zone_ The `async` function arranges for the tester's code to run in a special _async test zone_
that hides the mechanics of asynchronous execution, just as it does when passed to an [_it_ test)(#async). that hides the mechanics of asynchronous execution, just as it does when passed to an [_it_ test)(#async).
a#compile-components #compile-components
:marked :marked
## _compileComponents_ ## _compileComponents_
In this example, `Testbed.compileComponents` compiles one component, the `DashboardComponent`. In this example, `Testbed.compileComponents` compiles one component, the `DashboardComponent`.
@ -884,7 +884,7 @@ a#compile-components
.l-hr .l-hr
a#component-with-inputs-outputs #component-with-inputs-outputs
:marked :marked
# Test a component with inputs and outputs # Test a component with inputs and outputs
A component with inputs and outputs typically appears inside the view template of a host component. A component with inputs and outputs typically appears inside the view template of a host component.
@ -971,7 +971,7 @@ a#component-with-inputs-outputs
.l-hr .l-hr
a#component-inside-test-host #component-inside-test-host
:marked :marked
# Test a component inside a test host component # Test a component inside a test host component
@ -1015,7 +1015,7 @@ a(href="#top").to-top Back to top
.l-hr .l-hr
a#routed-component #routed-component
:marked :marked
# Test a routed component # Test a routed component
@ -1041,7 +1041,7 @@ a#routed-component
The following test clicks the displayed hero and confirms (with the help of a spy) that `Router.navigateByUrl` is called with the expected url. The following test clicks the displayed hero and confirms (with the help of a spy) that `Router.navigateByUrl` is called with the expected url.
+makeExample('testing/ts/app/dashboard/dashboard.component.spec.ts', 'navigate-test', 'app/dashboard/dashboard.component.spec.ts (navigate test)')(format='.') +makeExample('testing/ts/app/dashboard/dashboard.component.spec.ts', 'navigate-test', 'app/dashboard/dashboard.component.spec.ts (navigate test)')(format='.')
a#inject #inject
:marked :marked
## The _inject_ function ## The _inject_ function
@ -1083,7 +1083,7 @@ a(href="#top").to-top Back to top
.l-hr .l-hr
a#routed-component-w-param #routed-component-w-param
:marked :marked
# Test a routed component with parameters # Test a routed component with parameters
@ -1117,7 +1117,7 @@ a#routed-component-w-param
By now you know how to stub the `Router` and a data service. By now you know how to stub the `Router` and a data service.
Stubbing the `ActivatedRoute` would follow the same pattern except for a complication: Stubbing the `ActivatedRoute` would follow the same pattern except for a complication:
the `ActivatedRoute.params` is an _Observable_. the `ActivatedRoute.params` is an _Observable_.
a#stub-observable #stub-observable
:marked :marked
### _Observable_ test double ### _Observable_ test double
@ -1148,7 +1148,7 @@ a#stub-observable
:marked :marked
The router stubs in this chapter are meant to inspire you. Create your own stubs to fit your testing needs. The router stubs in this chapter are meant to inspire you. Create your own stubs to fit your testing needs.
a#observable-tests #observable-tests
:marked :marked
### _Observable_ tests ### _Observable_ tests
Here's a test demonstrating the component's behavior when the observed `id` refers to an existing hero: Here's a test demonstrating the component's behavior when the observed `id` refers to an existing hero:
@ -1177,7 +1177,7 @@ a#observable-tests
.l-hr .l-hr
a#page-object #page-object
:marked :marked
# Use a _page_ object to simplify setup # Use a _page_ object to simplify setup
@ -1214,24 +1214,23 @@ figure.image-display
a(href="#top").to-top Back to top a(href="#top").to-top Back to top
.l-hr .l-hr
a#router-outlet-component #router-outlet-component
:marked :marked
# Test a _RouterOutlet_ component # Test a _RouterOutlet_ component
The `AppComponent` displays routed components in a `<router-outlet>`. The `AppComponent` displays routed components in a `<router-outlet>`.
It also displays a navigation bar with anchors and their `RouterLink` directives. It also displays a navigation bar with anchors and their `RouterLink` directives.
a#app-component-html #app-component-html
+makeExample('testing/ts/app/app.component.html', '', 'app/app.component.html')(format='.') +makeExample('testing/ts/app/app.component.html', '', 'app/app.component.html')(format='.')
:marked :marked
The component class does nothing. The component class does nothing.
+makeExample('testing/ts/app/app.component.ts', '', 'app/app.component.ts')(format='.') +makeExample('testing/ts/app/app.component.ts', '', 'app/app.component.ts')(format='.')
.l-sub-section
:marked
This component may not seem worth testing but it's instructive to show how to test it.
:marked :marked
Unit tests can confirm that the anchors are wired properly without engaging the router. Unit tests can confirm that the anchors are wired properly without engaging the router.
See why this is worth doing [below](#why-stubbed-routerlink-tests).
a#stub-component #stub-component
:marked :marked
### Stubbing unneeded components ### Stubbing unneeded components
@ -1251,7 +1250,7 @@ a#stub-component
The component stubs are essential. The component stubs are essential.
Without them, the Angular compiler doesn't recognize the `<app-welcome>` and `<router-outlet>` tags Without them, the Angular compiler doesn't recognize the `<app-welcome>` and `<router-outlet>` tags
and throws an error. and throws an error.
a#router-link-stub #router-link-stub
:marked :marked
### Stubbing the _RouterLink_ ### Stubbing the _RouterLink_
@ -1263,8 +1262,8 @@ a#router-link-stub
Clicking the anchor should trigger the `onClick` method which sets the telltale `navigatedTo` property. Clicking the anchor should trigger the `onClick` method which sets the telltale `navigatedTo` property.
Tests can inspect that property to confirm the expected _click-to-navigation_ behavior. Tests can inspect that property to confirm the expected _click-to-navigation_ behavior.
a#by-directive #by-directive
a#inject-directive #inject-directive
:marked :marked
### _By.directive_ and injected directives ### _By.directive_ and injected directives
@ -1278,7 +1277,7 @@ a#inject-directive
1. You can use the component's dependency injector to get an attached directive because 1. You can use the component's dependency injector to get an attached directive because
Angular always adds attached directives to the component's injector. Angular always adds attached directives to the component's injector.
a#app-component-tests #app-component-tests
:marked :marked
Here are some tests that leverage this setup: Here are some tests that leverage this setup:
+makeExample('testing/ts/app/app.component.spec.ts', 'tests', 'app/app.component.spec.ts (selected tests)')(format='.') +makeExample('testing/ts/app/app.component.spec.ts', 'tests', 'app/app.component.spec.ts (selected tests)')(format='.')
@ -1295,10 +1294,28 @@ a#app-component-tests
This is a skill you may need to test a more sophisticated component, one that changes the display, This is a skill you may need to test a more sophisticated component, one that changes the display,
re-calculates parameters, or re-arranges navigation options when the user clicks the link. re-calculates parameters, or re-arranges navigation options when the user clicks the link.
#why-stubbed-routerlink-tests
:marked
### What good are these tests?
Stubbed `RouterLink` tests can confirm that a component with links and an outlet is setup properly,
that the component has the links it should have and
that they are all pointing in the expected direction.
These tests do not concern whether the app will succeed in navigating to the target component when the user clicks a link.
Stubbing the RouterLink and RouterOutlet is the best option for such limited testing goals.
Relying on the real router would make them brittle.
They could fail for reasons unrelated to the component.
For example, a navigation guard could prevent an unauthorized user from visiting the `HeroListComponent`.
That's not the fault of the `AppComponent` and no change to that component could cure the failed test.
A _different_ battery of tests can explore whether the application navigates as expected
in the presence of conditions that influence guards such as whether the user is authenticated and authorized.
A future chapter update will explain how to write such tests with the `RouterTestingModule`.
a(href="#top").to-top Back to top a(href="#top").to-top Back to top
.l-hr .l-hr
a#shallow-component-test #shallow-component-test
:marked :marked
# "Shallow component tests" with *NO\_ERRORS\_SCHEMA* # "Shallow component tests" with *NO\_ERRORS\_SCHEMA*
@ -1332,7 +1349,7 @@ a(href="#top").to-top Back to top
.l-hr .l-hr
a#attribute-directive #attribute-directive
:marked :marked
# Test an attribute directive # Test an attribute directive
@ -1392,8 +1409,8 @@ a(href="#top").to-top Back to top
.l-hr .l-hr
a#isolated-tests #isolated-tests
a#testing-without-atp #testing-without-atp
:marked :marked
# Testing without the Angular Testing Platform # Testing without the Angular Testing Platform
@ -1423,7 +1440,7 @@ a#testing-without-atp
Write _Angular_ tests to validate the part as it interacts with Angular, Write _Angular_ tests to validate the part as it interacts with Angular,
updates the DOM, and collaborates with the rest of the application. updates the DOM, and collaborates with the rest of the application.
a#isolated-service-tests #isolated-service-tests
:marked :marked
## Services ## Services
Services are good candidates for isolated unit testing. Services are good candidates for isolated unit testing.
@ -1474,7 +1491,7 @@ a#isolated-service-tests
Use the Angular Testing Platform when writing tests that validate how a service interacts with components Use the Angular Testing Platform when writing tests that validate how a service interacts with components
_within the Angular runtime environment_. _within the Angular runtime environment_.
a#isolated-pipe-tests #isolated-pipe-tests
:marked :marked
## Pipes ## Pipes
Pipes are easy to test without the Angular Testing Platform (ATP). Pipes are easy to test without the Angular Testing Platform (ATP).
@ -1500,7 +1517,7 @@ a#isolated-pipe-tests
Consider adding ATP component tests such as this one. Consider adding ATP component tests such as this one.
+makeExample('testing/ts/app/hero/hero-detail.component.spec.ts', 'title-case-pipe', 'app/hero/hero-detail.component.spec.ts (pipe test)') +makeExample('testing/ts/app/hero/hero-detail.component.spec.ts', 'title-case-pipe', 'app/hero/hero-detail.component.spec.ts (pipe test)')
a#isolated-component-tests #isolated-component-tests
:marked :marked
## Components ## Components
@ -1537,7 +1554,7 @@ a(href="#top").to-top Back to top
.l-hr .l-hr
a#atp-api #atp-api
:marked :marked
# Angular Testing Platform APIs # Angular Testing Platform APIs
@ -1629,7 +1646,7 @@ table
.l-hr .l-hr
a#testbed-class-summary #testbed-class-summary
:marked :marked
# _TestBed_ Class Summary # _TestBed_ Class Summary
The `TestBed` class is a principle feature of the _Angular Testing Platform_. The `TestBed` class is a principle feature of the _Angular Testing Platform_.
@ -1659,7 +1676,7 @@ code-example(format="." language="javascript").
}; };
:marked :marked
a#testbed-methods #testbed-methods
:marked :marked
The `TestBed` API consists of static class methods that either update or reference a _global_ instance of the`TestBed`. The `TestBed` API consists of static class methods that either update or reference a _global_ instance of the`TestBed`.
@ -1677,7 +1694,7 @@ table
td td
:marked :marked
The testing shims (`karma-test-shim`, `browser-test-shim`) The testing shims (`karma-test-shim`, `browser-test-shim`)
establish the [initial test environment](#a#testbed-initTestEnvironment) and a default testing module. establish the [initial test environment](##testbed-initTestEnvironment) and a default testing module.
The default testing module is configured with basic declaratives and some Angular service substitutes (e.g. `DebugDomRender`) The default testing module is configured with basic declaratives and some Angular service substitutes (e.g. `DebugDomRender`)
that every tester needs. that every tester needs.
@ -1771,7 +1788,7 @@ table
A few of the `TestBed` instance methods are not covered by static `TestBed` _class_ methods. A few of the `TestBed` instance methods are not covered by static `TestBed` _class_ methods.
These are rarely needed. These are rarely needed.
a#component-fixture-api-summary #component-fixture-api-summary
:marked :marked
## The _ComponentFixture_ ## The _ComponentFixture_
@ -1782,7 +1799,7 @@ a#component-fixture-api-summary
The `ComponentFixture` properties and methods provide access to the component, The `ComponentFixture` properties and methods provide access to the component,
its DOM representation, and aspects of its Angular environment. its DOM representation, and aspects of its Angular environment.
a#component-fixture-properties #component-fixture-properties
:marked :marked
### _ComponentFixture_ properties ### _ComponentFixture_ properties
@ -1820,7 +1837,7 @@ table
component that has the `ChangeDetectionStrategy.OnPush` component that has the `ChangeDetectionStrategy.OnPush`
or the component's change detection is under your programmatic control. or the component's change detection is under your programmatic control.
a#component-fixture-methods #component-fixture-methods
:marked :marked
### _ComponentFixture_ methods ### _ComponentFixture_ methods
@ -1887,7 +1904,7 @@ table
:marked :marked
Trigger component destruction. Trigger component destruction.
a#debug-element-details #debug-element-details
:marked :marked
### _DebugElement_ ### _DebugElement_
@ -2003,7 +2020,7 @@ table
Dictionary of objects associated with template local variables (e.g. `#foo`), Dictionary of objects associated with template local variables (e.g. `#foo`),
keyed by the local variable name. keyed by the local variable name.
a#query-predicate #query-predicate
:marked :marked
The `DebugElement.query(predicate)` and `DebugElement.queryAll(predicate)` methods take a The `DebugElement.query(predicate)` and `DebugElement.queryAll(predicate)` methods take a
predicate that filters the source element's subtree for matching `DebugElement`. predicate that filters the source element's subtree for matching `DebugElement`.
@ -2020,7 +2037,7 @@ a#query-predicate
+makeExample('testing/ts/app/hero/hero-list.component.spec.ts', 'by', 'app/hero/hero-list.component.spec.ts')(format=".") +makeExample('testing/ts/app/hero/hero-list.component.spec.ts', 'by', 'app/hero/hero-list.component.spec.ts')(format=".")
a#renderer-tests #renderer-tests
:marked :marked
Many custom application directives inject the `Renderer` and call one of its `set...` methods. Many custom application directives inject the `Renderer` and call one of its `set...` methods.
@ -2068,7 +2085,7 @@ a(href="#top").to-top Back to top
.l.hr .l.hr
a#faq #faq
.l-main-section .l-main-section
:marked :marked
## FAQ: Frequently Asked Questions ## FAQ: Frequently Asked Questions
@ -2106,7 +2123,7 @@ a(href="#top").to-top Back to top
.l-hr .l-hr
a#q-spec-file-location #q-spec-file-location
:marked :marked
### Why put specs next to the things they test? ### Why put specs next to the things they test?
@ -2120,7 +2137,7 @@ a#q-spec-file-location
.l-hr .l-hr
a#q-specs-in-test-folder #q-specs-in-test-folder
:marked :marked
### When would I put specs in a test folder? ### When would I put specs in a test folder?