mirror of https://github.com/abpframework/abp.git
2 changed files with 271 additions and 0 deletions
@ -0,0 +1,121 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn how runtime configuration and environment variables work in ABP React UI applications." |
|||
} |
|||
``` |
|||
|
|||
# Environment Variables |
|||
|
|||
ABP React UI applications use a runtime configuration file and Vite environment variables together. The template is preconfigured by ABP Studio's modern wizard, available with ABP Studio **v3.0+**, so a newly created solution already contains working local values for the API, Auth Server, OpenIddict client, and Admin Console link. |
|||
|
|||
You usually change these values when moving the application to another environment such as staging or production. |
|||
|
|||
## Configuration Sources |
|||
|
|||
The React template reads configuration from these places: |
|||
|
|||
- `dynamic-env.json`: runtime configuration that can be changed without rebuilding the application. |
|||
- `public/dynamic-env.json`: the file served by the app. The Vite build copies the root `dynamic-env.json` into this location when it exists. |
|||
- `src/env.ts`: local fallback values used when runtime configuration is not loaded. |
|||
- `.env` files / shell variables: Vite variables such as `VITE_API_URL`, `VITE_AUTH_URL`, and `VITE_APP_URL`. |
|||
|
|||
For layered and single-layer modern templates, the React app is in the `react/` folder. For the microservice modern template, it is in `apps/react/`. |
|||
|
|||
## `dynamic-env.json` |
|||
|
|||
The runtime configuration file has the same purpose as Angular's dynamic environment configuration: it lets you deploy the same build artifact to different environments and change the API or authentication endpoints at runtime. |
|||
|
|||
```json |
|||
{ |
|||
"application": { |
|||
"baseUrl": "https://localhost:3000", |
|||
"name": "Acme.BookStore" |
|||
}, |
|||
"oAuthConfig": { |
|||
"issuer": "https://localhost:44301/", |
|||
"redirectUri": "https://localhost:3000", |
|||
"clientId": "Acme_BookStore_App", |
|||
"scope": "offline_access openid profile email phone AuthServer IdentityService AdministrationService" |
|||
}, |
|||
"apis": { |
|||
"default": { |
|||
"url": "https://localhost:44300", |
|||
"rootNamespace": "Acme.BookStore" |
|||
} |
|||
}, |
|||
"adminConsoleUrl": "https://localhost:44307" |
|||
} |
|||
``` |
|||
|
|||
The template loads `/dynamic-env.json` first and then tries `/getEnvConfig` for compatibility with environments that expose the file through that endpoint. |
|||
|
|||
## Available Values |
|||
|
|||
| Key | Description | |
|||
| --- | --- | |
|||
| `application.baseUrl` | Public URL of the React application. It is used as a fallback for OAuth redirect URLs. | |
|||
| `application.name` | Application name. | |
|||
| `application.logoUrl` | Optional logo URL for application branding. | |
|||
| `oAuthConfig.issuer` | Auth Server / OpenIddict authority URL. | |
|||
| `oAuthConfig.redirectUri` | Redirect URI registered for the React OpenIddict client. | |
|||
| `oAuthConfig.clientId` | OpenIddict client ID. The main React app uses `<ProjectName>_App`. | |
|||
| `oAuthConfig.scope` | OAuth scopes requested by the SPA. | |
|||
| `apis.default.url` | Backend API base URL. In microservice solutions, this normally points to the Web Gateway. | |
|||
| `apis.default.rootNamespace` | Root namespace used by generated API code and module-specific clients. | |
|||
| `adminConsoleUrl` | Origin of the Admin Console app. The React template uses it to open `/admin-console`. | |
|||
|
|||
The `DynamicEnv` type also includes fields such as `production`, `oAuthConfig.requireHttps`, `oAuthConfig.responseType`, `oAuthConfig.strictDiscoveryDocumentValidation`, and `oAuthConfig.skipIssuerCheck`. The template's OIDC setup always uses the Authorization Code flow by setting `responseType` to `code`. |
|||
|
|||
## Vite Variables |
|||
|
|||
The React template uses Vite and reads environment variables with `loadEnv(mode, process.cwd(), '')`, so variables are not limited to the `VITE_` prefix inside `vite.config.ts`. |
|||
|
|||
The important variables for developers are: |
|||
|
|||
| Variable | Description | |
|||
| --- | --- | |
|||
| `VITE_API_URL` | Overrides the backend API or gateway URL used by the dev proxy and runtime fallback. | |
|||
| `VITE_AUTH_URL` | Overrides the Auth Server URL used by the dev proxy and runtime fallback. If omitted, the dev proxy can fall back to `VITE_API_URL`. | |
|||
| `VITE_APP_URL` | Overrides the React app URL used as the OAuth redirect URI fallback. | |
|||
|
|||
Example: |
|||
|
|||
```bash |
|||
VITE_API_URL=https://api.bookstore.example.com |
|||
VITE_AUTH_URL=https://auth.bookstore.example.com |
|||
VITE_APP_URL=https://bookstore.example.com |
|||
``` |
|||
|
|||
## What ABP Studio Preconfigures |
|||
|
|||
When a React solution is created with ABP Studio v3.0+ or `abp new --modern`, the template fills these values from the generated solution configuration: |
|||
|
|||
- Local launch ports for the React app, Web Gateway/API host, Auth Server, and Admin Console. |
|||
- The OpenIddict client ID, usually `<ProjectName>_App`. |
|||
- OAuth scopes based on the selected modules, such as Identity, Administration, SaaS, Audit Logging, GDPR, File Management, AI Management, Language Management, or Chat. |
|||
- `adminConsoleUrl` when the template includes a separate Admin Console application. |
|||
|
|||
For local development, these generated values should work without manual changes. For production, update the API URL, Auth Server URL, redirect URI, client ID if you changed the seeded client, and any environment-specific scopes. |
|||
|
|||
## Development Proxy |
|||
|
|||
In development, `vite.config.ts` proxies these paths: |
|||
|
|||
- `/api` to `VITE_API_URL` or the generated API/gateway URL. |
|||
- `/connect` to `VITE_AUTH_URL`, `VITE_API_URL`, or the generated Auth Server URL. |
|||
- `/getEnvConfig` to `VITE_API_URL` or the generated API/gateway URL. |
|||
|
|||
This allows the React app to call same-origin paths during development while the backend services run on their own ports. |
|||
|
|||
## Deployment |
|||
|
|||
For deployment, prefer changing `dynamic-env.json` instead of rebuilding the React application for each environment. The file should be served with `application/json` content type and should not be rewritten to `index.html` by SPA fallback rules. |
|||
|
|||
If your server exposes `/getEnvConfig`, configure it to return the same JSON content as `dynamic-env.json`. |
|||
|
|||
## See Also |
|||
|
|||
- [React UI](./index.md) |
|||
- [Authorization](./authorization.md) |
|||
- [HTTP Requests](./http-requests.md) |
|||
@ -0,0 +1,150 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Learn how to run and write unit tests in ABP React UI applications with Vitest and React Testing Library." |
|||
} |
|||
``` |
|||
|
|||
# Unit Testing React UI |
|||
|
|||
ABP React UI templates are preconfigured for unit testing. A solution created with ABP Studio v3.0+ or `abp new --modern --ui-framework react` includes Vitest, jsdom, React Testing Library, and jest-dom. |
|||
|
|||
You can add a test file and run the test command without adding extra test infrastructure. |
|||
|
|||
## Test Stack |
|||
|
|||
The React template uses: |
|||
|
|||
| Package | Purpose | |
|||
| --- | --- | |
|||
| `vitest` | Test runner and assertion library. | |
|||
| `jsdom` | Browser-like DOM environment for component tests. | |
|||
| `@testing-library/react` | Render React components and query the DOM like a user. | |
|||
| `@testing-library/jest-dom` | Extra DOM assertions such as `toBeInTheDocument`. | |
|||
|
|||
The template also includes `src/test/setup.ts`, which imports `@testing-library/jest-dom/vitest` and initializes the React i18n setup. |
|||
|
|||
## Configuration |
|||
|
|||
The test configuration is in `vitest.config.ts`: |
|||
|
|||
```ts |
|||
import { defineConfig } from 'vitest/config' |
|||
import react from '@vitejs/plugin-react' |
|||
import path from 'path' |
|||
|
|||
export default defineConfig({ |
|||
plugins: [react()], |
|||
test: { |
|||
environment: 'jsdom', |
|||
setupFiles: ['./src/test/setup.ts'], |
|||
include: ['src/**/*.{test,spec}.{ts,tsx}'], |
|||
globals: true, |
|||
}, |
|||
resolve: { |
|||
alias: { |
|||
'@': path.resolve(__dirname, './src'), |
|||
}, |
|||
}, |
|||
}) |
|||
``` |
|||
|
|||
Tests can import application files with the same `@/` alias used by the app. |
|||
|
|||
## Running Tests |
|||
|
|||
Install dependencies once: |
|||
|
|||
```bash |
|||
npm install |
|||
``` |
|||
|
|||
Run tests in watch mode: |
|||
|
|||
```bash |
|||
npm run test |
|||
``` |
|||
|
|||
Run tests once, which is useful for CI: |
|||
|
|||
```bash |
|||
npm run test:run |
|||
``` |
|||
|
|||
The template's `package.json` maps these commands to `vitest` and `vitest run`. |
|||
|
|||
## Example Test |
|||
|
|||
The template includes example tests under `src/`. For example, `src/pages/home/HomePage.test.tsx` renders the home page and mocks the authentication hook: |
|||
|
|||
```tsx |
|||
import { describe, it, expect, vi, beforeEach } from 'vitest' |
|||
import { render, screen } from '@testing-library/react' |
|||
import { HomePage } from './HomePage' |
|||
import * as auth from '@/lib/auth/AuthContext' |
|||
|
|||
vi.mock('@/lib/auth/AuthContext', () => ({ |
|||
useAuth: vi.fn(), |
|||
})) |
|||
|
|||
describe('HomePage', () => { |
|||
beforeEach(() => { |
|||
vi.clearAllMocks() |
|||
}) |
|||
|
|||
it('renders login prompt when not authenticated', () => { |
|||
vi.mocked(auth.useAuth).mockReturnValue({ |
|||
isAuthenticated: false, |
|||
isLoading: false, |
|||
user: null, |
|||
login: vi.fn(), |
|||
logout: vi.fn(), |
|||
navigateToLogin: vi.fn(), |
|||
getAccessToken: vi.fn(), |
|||
} as unknown as ReturnType<typeof auth.useAuth>) |
|||
|
|||
render(<HomePage />) |
|||
expect(screen.getByText('Welcome')).toBeInTheDocument() |
|||
expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument() |
|||
}) |
|||
}) |
|||
``` |
|||
|
|||
This style keeps the test focused on visible behavior. Dependencies that would require real authentication, network calls, or browser redirects are mocked. |
|||
|
|||
## Writing a Component Test |
|||
|
|||
Create a `*.test.tsx` file next to the component: |
|||
|
|||
```tsx |
|||
import { render, screen } from '@testing-library/react' |
|||
import { describe, expect, it } from 'vitest' |
|||
import { Button } from '@/components/ui/button' |
|||
|
|||
describe('Button', () => { |
|||
it('renders its content', () => { |
|||
render(<Button>Save</Button>) |
|||
expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument() |
|||
}) |
|||
}) |
|||
``` |
|||
|
|||
Prefer queries such as `getByRole`, `getByLabelText`, and `getByText` because they describe what the user can see or do. |
|||
|
|||
## Writing a Service or Hook Test |
|||
|
|||
For non-component logic, use Vitest directly. The template includes tests for routing guards, permissions, authentication context, and Axios interceptors. |
|||
|
|||
When testing API code, mock the shared Axios instance or the lower-level dependency instead of calling a real backend. When testing permission behavior, mock the application configuration client or use the exported permission helpers. |
|||
|
|||
## Interpreting Output |
|||
|
|||
Vitest reports each test file, failed assertions, stack traces, and a summary of passed/failed tests. In watch mode, it reruns affected tests when files change. In `test:run` mode, Vitest exits with a non-zero status code if any test fails, which makes it suitable for CI pipelines. |
|||
|
|||
If a component test fails because an ABP service is not initialized, mock the hook or provider used by the component. For example, pages that call `useAuth()` or `usePermissions()` should provide a controlled mock for those hooks unless the test is specifically verifying the provider. |
|||
|
|||
## See Also |
|||
|
|||
- [Components](./components/index.md) |
|||
- [Authorization](./authorization.md) |
|||
- [Permission Management](./permission-management.md) |
|||
Loading…
Reference in new issue