Hi Pact, I’m in the process of upgrading an angul...
# general
k
Hi Pact, I’m in the process of upgrading an angular app from spec version 2 to version 3 using the migration documentation. Now that mockProvider (PactV3) no longer has access to mockService, where can I access the baseUrl? The migration docs claim:
Copy code
the mockService property on the Pact class is no longer an actual MockService, but supports the baseUrl property for compatibility.
But I am seeing an error that mockService is not a known property of PactV3. What are my options here?
👋 1
y
It's available within the
executeTest
scope as shown in the linked docs so this
Copy code
const api = new API(provider.mockService.baseUrl);

            // make request to Pact mock server
            const product = await api.getAllProducts();

            expect(product).toStrictEqual([
                {"id": "09", "name": "Gem Visa", "type": "CREDIT_CARD"}
            ]);
would become this
Copy code
await provider.executeTest(async (mockService) => {
                const api = new API(mockService.url);

                // make request to Pact mock server
                const product = await api.getAllProducts();

                expect(product).toStrictEqual([
                    {"id": "09", "name": "Gem Visa", "type": "CREDIT_CARD"}
                ]);
            })
e
Hi @Kevin Grady I had the same problem. I added an Interceptor to the TestBed-Environment:
Copy code
@Injectable()
export class DynamicUrlTestInterceptor implements HttpInterceptor {
    constructor(private urlResolver: () => string | undefined) {}
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const urlToPrepend = this.urlResolver();
        if (urlToPrepend) {
            request = request.clone({
                url: urlToPrepend + request.url,
            });
        }
        return next.handle(request);
    }
}
Add it to your Test-Env:
Copy code
{
    provide: HTTP_INTERCEPTORS,
    useValue: new DynamicUrlTestInterceptor(() => mockServerUrl),
    multi: true,
},
In my test-case:
Copy code
const angebotIntegrationService = TestBed.inject(AngebotIntegrationService);
await providerV3.executeTest(async (mockServer) => {
    mockServerUrl = mockServer.url; // activate DynamicUrlTestInterceptor
    const response = await firstValueFrom(
        angebotIntegrationService.createAngebotNeugeschaeftRsvUnternehmen(
            angeboteErstellenNeugeschaeftRsvUnternehmen,
            captchaToken
        )
    );
Need to add this to your test as well:
Copy code
let mockServerUrl: string | undefined = undefined;
Basically, I set the
mockServerUrl = mockServer.url;
in
executeTest()
, for any subsequent Http-Request, the Interceptor will be called, which will in turn call the
urlResolver
callback to get the current-value to use at that moment, and change the url accordingly.
let me know if you found a better solution
k
@Erich Buri With your solution, where is
mockServerUrl
being passed? If I assign it within
executeTest()
it has no place to go and is just an unused variable
@Yousaf Nabi (pactflow.io) The API that I am testing does not take a URL as a parameter when instantiating like in
const api = new API(mockService.url);
but takes an
HttpService
and
HttpConfigService
which have been added as providers in a
beforeEach()
. If that is the case, can I simply leave the API instantiation out of the
executeTest()
method? I still need access to a mockUrl for the provider `ConfigService`’s
useValue
Here’s a look at my
beforeEach
untouched. Error is on
mockProvider.mockService
which no longer exists:
Copy code
beforeEach(async () => {
    const app = await Test.createTestingModule({
      imports: [ConfigModule, HttpModule],
      providers: [
        TenantService,
        HttpConfigService,
        {
          provide: 'MOCK_ENV',
          useValue: false,
        },
        {
          provide: ConfigService,
          useValue: {
            get: jest.fn((key: string) => {
              if (key === 'TENANT_BASE_URL') {
                return mockProvider.mockService.baseUrl;
              }
            }),
          },
        },
      ],
    }).compile();

    service = app.get<TenantService>(TenantService);
  });
e
I don't know
HttpService
- we're using
HttpClient
. But looking at your setup, you would have to return the actual value here:
Copy code
{
          provide: ConfigService,
          useValue: {
            get: jest.fn((key: string) => {
              if (key === 'TENANT_BASE_URL') {
                return mockProvider.mockService.baseUrl;
              }
            }),
          },
Except that you use a variable like
mockServerUrl
in my case (instead of
mockProvider.mockService.baseUrl
) mockServerUrl will then be set inside
executeTest()
k
So now I’ve got:
Copy code
provide: ConfigService,
          useValue: {
            get: jest.fn((key: string) => {
              if (key === 'TENANT_BASE_URL') {
                return new DynamicUrlTestInterceptor(() => mockServerUrl);
              }
            }),
          },
after implementing the DynamicUrlTestInterceptor. I assign the mockServerUrl in
executeTest()
but then the mockServerUrl isn’t referenced after it is assigned so it seems useless:
Copy code
await mockProvider.executeTest(async mockServer => {
        mockServerUrl = mockServer.url;
        const tenant = await lastValueFrom(service.createTenant(requestHeaders, reqBody));
        expect(tenant).toStrictEqual(camelCaseObjectKeys(responseBody));
      });
The test result in a connection error I assume is caused by the Url not working properly:
Copy code
+   "code": "ECONNREFUSED",
    +   "data": undefined,
    +   "message": "connect ECONNREFUSED ::1:80",
    +   "status": undefined,
    +   "statusText": undefined,
e
This is not what I suggested:
Copy code
provide: ConfigService,
          useValue: {
            get: jest.fn((key: string) => {
              if (key === 'TENANT_BASE_URL') {
                return new DynamicUrlTestInterceptor(() => mockServerUrl);
              }
            }),
          },
You're ConfigService returning an Interceptor does not make sense.
Copy code
{
          provide: ConfigService,
          useValue: {
            get: jest.fn((key: string) => {
              if (key === 'TENANT_BASE_URL') {
                return mockProvider.mockService.baseUrl;
              }
            }),
          },
Did you try to return the variable mockServerUrl here? You need to show me your Service-Code or find out at what time in the Lifecycle of your service it will call configService.get('TENANT_BASE_URL'). Anyway, if your Service eventually uses Angular HttpClient, you can just add this.
Copy code
{
    provide: HTTP_INTERCEPTORS,
    useValue: new DynamicUrlTestInterceptor(() => mockServerUrl),
    multi: true,
},
Replace
() => mockServerUrl
with
() => { console.log('Will now use url: ', mockServerUrl); return mockServerUrl }
and you will see in your log at what time the Url actually gets used.
k
Did you try to return the variable mockServerUrl here?
Yes I had the same error results
Anyway, if your Service eventually uses Angular HttpClient, you can just add this.
I’m getting a type mismatch error:
Copy code
Type 'InjectionToken<HttpInterceptor[]>' is not assignable to type 'InjectionToken'.
  Type 'InjectionToken<HttpInterceptor[]>' is missing the following properties from type 'Abstract<any>': prototype, apply, call, bind, and 5 more.
our main app module is using HttpClient but HttpConfigService is not. The HttpConfigService just determines if we are using local path for dummy data or not, for example:
Copy code
constructor(@Inject(MOCK_ENV) private mockEnv: boolean, private configService: ConfigService) {}

  private baseUrl = this.configService.get('TENANT_BASE_URL');
  private tenantUrl = `${this.baseUrl}/tenant`;

  public getTenant(): string {
    const config: httpConfig = {
      path: this.tenantUrl,
      localPath: '/get-tenant.json',
    };
    return this.getPath(config);
  }
which is where configService.get(’TENANT_BASE_URL) is being called
@Erich Buri I haven’t been able to figure out how to implement the interceptor due to this type issue. Let me know if you have any input, thank you!
e
@Kevin Grady The Interceptor is here: https://pact-foundation.slack.com/archives/C5F4KFKR8/p1679495404915779?thread_ts=1679354578.320799&amp;cid=C5F4KFKR8 Add it like this
Copy code
let mockServerUrl = undefined;

beforeEach(async () => {
    mockServerUrl = undefined;
    const app = await Test.createTestingModule({
      imports: [ConfigModule, HttpModule],
      providers: [
        TenantService,
        HttpConfigService,
        {
          provide: 'MOCK_ENV',
          useValue: false,
        },
        {
          provide: HTTP_INTERCEPTORS,
          useValue: new DynamicUrlTestInterceptor(() => mockServerUrl),
          multi: true,
        },
...
In your test:
Copy code
await mockProvider.executeTest(async mockServer => {
        mockServerUrl = mockServer.url;
        const tenant = await lastValueFrom(service.createTenant(requestHeaders, reqBody));
        expect(tenant).toStrictEqual(camelCaseObjectKeys(responseBody));
      });
There's nothing I can add to that. The Injection-Token HTTP_INTERCEPTORS and the Base-Class HttpInterceptor are from Angular itself. Maybe add a console.log()-Statement in the intercept()-Method of the DynamicUrlTestInterceptor to better understand what happens and to verify that the actual request comes through there.