Регистрация  |  Вход

Зачем нужен Mock-класс в тестировании коллаутов?

Не бросайтесь тапками: я только учусь Перерываю гайды: много информации о том, КАК сделать, но никак не могу найти, ЗАЧЕМ ЭТО. То есть почему мы не можем подрубить юнит-тест напрямую к коллауту? Что он нам дает? Еще возникла такая интересная фишка: я сейчас тестирую POST, GET, PUT и DELETE коллауты. Прописала мок на токен, если у нас в эндпоинте services/oauth2/token, а также сам объект в стринге, если задан иной эндпоинт:

@isTest
global class CalloutMock implements HttpCalloutMock {
global HTTPResponse respond(HTTPRequest request) {

HttpResponse response = new HttpResponse();
response.setHeader('Content-Type', 'application/json');
String serialized;
if(request.getEndpoint().contains('services/oauth2/token')) {
serialized = '{"webtoken": "fake-token"}';
} else {
serialized = '{"Name":"LooksLikeName"}';
}
response.setBody(serialized);
response.setStatusCode(200);
return response;
}
}

Что теперь происходит. Я использую этот мок во всех методах юнит-теста, и каким-то образом код вообще не приходится менять, только методы разные подтягиваю из основного класса в тест:

@isTest
static void testPostCallout() {
//Это стринга с данными токена (один из методов теста):
String request = accessTokenBody();
TestObj__c testObj = new TestObj__c(Name = 'LooksLikeName');
insert testObj;

Test.startTest();
Test.setMock(HttpCalloutMock.class, new CalloutMock());
HttpResponse response = OurClassWithCallouts.postCallout(testObj.Id);
String actualValue = response.getBody();
String expectedValue = '{"Name":"LooksLikeName"}';
System.assertEquals(actualValue, expectedValue);
System.assertEquals(200, response.getStatusCode());
Test.stopTest();
}

@isTest
static void testPostCallout() {
String request = accessTokenBody();
TestObj__c testObj = new TestObj__c(Name = 'LooksLikeName');
insert testObj;

Test.startTest();
Test.setMock(HttpCalloutMock.class, new CalloutMock());
HttpResponse response = OurClassWithCallouts.deleteCallout(testObj.Id);
String actualValue = response.getBody();
String expectedValue = '{"Name":"LooksLikeName"}';
System.assertEquals(actualValue, expectedValue);
System.assertEquals(200, response.getStatusCode());
Test.stopTest();
}

///...и так далее со всеми методами (GET и PUT).

Меня это настораживает. Ну то есть то, что покрытие почти 100%, все правильно ассёртится, а код в самом тесте практически не меняется. Я делаю что-то неправильно? Не могли бы вы объяснить суть моков в связке с обычными юнит-тестами? А то до меня как до жирафа вся эта интеграция :D

Не бросайтесь тапками: я только учусь :) Перерываю гайды: много информации о том, КАК сделать, но никак не могу найти, ЗАЧЕМ ЭТО. То есть почему мы не можем подрубить юнит-тест напрямую к коллауту? Что он нам дает? Еще возникла такая интересная фишка: я сейчас тестирую POST, GET, PUT и DELETE коллауты. Прописала мок на токен, если у нас в эндпоинте [b]services/oauth2/token[/b], а также сам объект в стринге, если задан иной эндпоинт:
[code]
@isTest
global class CalloutMock implements HttpCalloutMock {
    global HTTPResponse respond(HTTPRequest request) {
        
        HttpResponse response = new HttpResponse();
    	response.setHeader('Content-Type', 'application/json');
    	String serialized;
    	if(request.getEndpoint().contains('services/oauth2/token')) {
      		serialized = '{"webtoken": "fake-token"}';
    	} else {
      		serialized = '{"Name":"LooksLikeName"}';
    	}
    	response.setBody(serialized);
    	response.setStatusCode(200);
    	return response;
    }
}
[/code]

Что теперь происходит. Я использую этот мок во всех методах юнит-теста, и каким-то образом код вообще не приходится менять, только методы разные подтягиваю из основного класса в тест:
[code]
    @isTest
    static void testPostCallout() {
        //Это стринга с данными токена (один из методов теста):
        String request = accessTokenBody();
        TestObj__c testObj = new TestObj__c(Name = 'LooksLikeName');
        insert testObj;

    	Test.startTest(); 
 	Test.setMock(HttpCalloutMock.class, new CalloutMock());
 	HttpResponse response = OurClassWithCallouts.postCallout(testObj.Id);
    	String actualValue = response.getBody();
    	String expectedValue = '{"Name":"LooksLikeName"}';
    	System.assertEquals(actualValue, expectedValue);
    	System.assertEquals(200, response.getStatusCode());
        Test.stopTest(); 
     }

    @isTest
    static void testPostCallout() {
        String request = accessTokenBody();
        TestObj__c testObj = new TestObj__c(Name = 'LooksLikeName');
        insert testObj;

    	Test.startTest(); 
 	Test.setMock(HttpCalloutMock.class, new CalloutMock());
 	HttpResponse response = OurClassWithCallouts.deleteCallout(testObj.Id);
    	String actualValue = response.getBody();
    	String expectedValue = '{"Name":"LooksLikeName"}';
    	System.assertEquals(actualValue, expectedValue);
    	System.assertEquals(200, response.getStatusCode());
        Test.stopTest(); 
     }

///...и так далее со всеми методами (GET и PUT).

[/code]

Меня это настораживает. Ну то есть то, что покрытие почти 100%, все правильно ассёртится, а код в самом тесте практически не меняется. Я делаю что-то неправильно? Не могли бы вы объяснить суть моков в связке с обычными юнит-тестами? А то до меня как до жирафа вся эта интеграция :D

во время теста не происходит настоящего колаута, и чтобы тестируемый код на этой строчке не упал (а также чтобы вернуть в Респонс что полезное, что будет работать ниже по-коду - так как с респонсом обычно что-то делают), для этого и создают этот Мок-респонс. Это то, что заменит настоящий респонс во время теста. Можно сделать веб серсис колаут класс и по-детски, используя Test.isRunningTest() не делать колаута во время теста, а тут же подменять его чем-нибудь, чтобы код дальше прошел и покрылся тестом. Но лучше делать все с HttpCalloutMock классом

во время теста не происходит настоящего колаута, и чтобы тестируемый код на этой строчке не упал (а также чтобы вернуть в Респонс что полезное, что будет работать ниже по-коду - так как с респонсом обычно что-то делают), для этого и создают этот Мок-респонс. Это то, что заменит настоящий респонс во время теста. Можно сделать веб серсис колаут класс и по-детски, используя Test.isRunningTest() не делать колаута во время теста, а тут же подменять его чем-нибудь, чтобы код дальше прошел и покрылся тестом. Но лучше делать все с HttpCalloutMock классом

Den Brown
во время теста не происходит настоящего колаута, и чтобы тестируемый код на этой строчке не упал (а также чтобы вернуть в Респонс что полезное, что будет работать ниже по-коду - так как с респонсом обычно что-то делают), для этого и создают этот Мок-респонс. Это то, что заменит настоящий респонс во время теста. Можно сделать веб серсис колаут класс и по-детски, используя Test.isRunningTest() не делать колаута во время теста, а тут же подменять его чем-нибудь, чтобы код дальше прошел и покрылся тестом. Но лучше делать все с HttpCalloutMock классом

Спасибо. Поняла. Ну в принципе Мок же может быть универсальным для всех методов, как в примере? Тест отрабатывает в принципе, и код покрывает.

[quote="Den Brown"]во время теста не происходит настоящего колаута, и чтобы тестируемый код на этой строчке не упал (а также чтобы вернуть в Респонс что полезное, что будет работать ниже по-коду - так как с респонсом обычно что-то делают), для этого и создают этот Мок-респонс. Это то, что заменит настоящий респонс во время теста. Можно сделать веб серсис колаут класс и по-детски, используя Test.isRunningTest() не делать колаута во время теста, а тут же подменять его чем-нибудь, чтобы код дальше прошел и покрылся тестом. Но лучше делать все с HttpCalloutMock классом[/quote]
Спасибо. Поняла. Ну в принципе Мок же может быть универсальным для всех методов, как в примере? Тест отрабатывает в принципе, и код покрывает.

При граммотном написании callout, mock можно и не делать.

При граммотном написании callout, mock можно и не делать.

wilder
При граммотном написании callout, mock можно и не делать.

Это как? Я знаю, что есть возможность тестировать Моками и Статик Ресурсами. Пытаюсь сейчас разобраться с Testing multiple HTTP callouts, но вообще, в интеграции путаюсь пока что.

[quote="wilder"]При граммотном написании callout, mock можно и не делать.[/quote]
Это как? Я знаю, что есть возможность тестировать Моками и Статик Ресурсами. Пытаюсь сейчас разобраться с Testing multiple HTTP callouts, но вообще, в интеграции путаюсь пока что.

Если сделать все каллауты из одного места. То в этом месте можно сделать проверку на текущий контекст. И если понятно что мы в тесте то из статик ресурса или из другого места подгрузать ответ соответствующий запросу

Если сделать все каллауты из одного места. То в этом месте можно сделать проверку на текущий контекст. И если понятно что мы в тесте то из статик ресурса или из другого места подгрузать ответ соответствующий запросу

Вот wilder про это хочет сказать

HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
Http http = new Http();
req.setEndpoint('some_url');
req.setMethod('GET');
if (!test.isRunningTest()) {
res = http.send(req);
} else {
res = new HttpResponse();
res.setHeader('Content-Type', 'application/json');
res.setBody('???');
res.setStatusCode(200);
}

Вот wilder про это хочет сказать

[code]
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
Http http = new Http();
req.setEndpoint('some_url');
req.setMethod('GET');
if (!test.isRunningTest()) {
	res = http.send(req);
} else {
	res = new HttpResponse();
	res.setHeader('Content-Type', 'application/json');
	res.setBody('???');
	res.setStatusCode(200);
}
[/code]

Конечно правильнее использовать Mock
но не всегда Mock работают и приходится этот вариант использовать.

Вот к примеру интересный случай для Mock.
Как протестировать метод который выполняет не один а несколько callouts.

Можно конечно разбить на несколько методов и тестить каждый отдельно. Но вот если нельзя, то что?

Конечно правильнее использовать Mock
но не всегда Mock работают и приходится этот вариант использовать.

Вот к примеру интересный случай для Mock.
Как протестировать метод который выполняет не один а несколько callouts.

Можно конечно разбить на несколько методов и тестить каждый отдельно. Но вот если нельзя, то что?

Stub фреймворк решает все проблемы)

Stub фреймворк решает все проблемы)

Gres
Stub фреймворк

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_stub_api.htm

Фигасе. Я даже не слышал про такое 0_о
Кто использует в реальных проектах? Какие еще преимущества он дает?

[quote="Gres"]Stub фреймворк[/quote]
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_stub_api.htm

Фигасе. Я даже не слышал про такое 0_о
Кто использует в реальных проектах? Какие еще преимущества он дает?

Dmitry Shnyrev
Кто использует в реальных проектах? Какие еще преимущества он дает?

Я использую уже давно, написал над ним свое удобное API, очень нравится мапить результат выполнения метода на какой-то стаб.

[quote="Dmitry Shnyrev"]Кто использует в реальных проектах? Какие еще преимущества он дает? [/quote]
Я использую уже давно, написал над ним свое удобное API, очень нравится мапить результат выполнения метода на какой-то стаб.

Угу. Так точно написан свой фреймворк для стабов. И он значительно проще чем по ссылке.

А стабы нужны для симуляции еще не готовых вебсервисов. Очень удобно через сеттингс включать/выключать.

Угу. Так точно написан свой фреймворк для стабов. И он значительно проще чем по ссылке.

А стабы нужны для симуляции еще не готовых вебсервисов. Очень удобно через сеттингс включать/выключать.

Dmitry Shnyrev
Как протестировать метод который выполняет не один а несколько callouts.

Возвращать разный мок для разных эндпоинтов: https://salesforce.stackexchange.com/questions/139235/how-to-create-mock-class-for-multiple-callouts-in-single-class

[quote="Dmitry Shnyrev"]
Как протестировать метод который выполняет не один а несколько callouts.
[/quote]

Возвращать разный мок для разных эндпоинтов: https://salesforce.stackexchange.com/questions/139235/how-to-create-mock-class-for-multiple-callouts-in-single-class

О блин! Оказывается все так просто.
Я даже чет и не додумался проверять endpoint запроса чтобы организовать универсальный Mock

private class Mock implements HttpCalloutMock {
public HTTPResponse respond(HTTPRequest req) {
if (req.getEndpoint().endsWith('abc')) {
HTTPResponse res = new HTTPResponse();
res.setBody('{}');
res.setStatusCode(200);
return res;
} else if (req.getEndpoint().endsWith('xyz')) {
...
} else {
System.assert(false, 'unexpected endpoint ' + req.getEndpoint());
}
}
}

О блин! Оказывается все так просто.
Я даже чет и не додумался проверять endpoint запроса чтобы организовать универсальный Mock

[code]
private class Mock implements HttpCalloutMock {
    public HTTPResponse respond(HTTPRequest req) {
        if (req.getEndpoint().endsWith('abc')) {
            HTTPResponse res = new HTTPResponse();
            res.setBody('{}');
            res.setStatusCode(200);
            return res;
        } else if (req.getEndpoint().endsWith('xyz')) {
            ...
        } else {
            System.assert(false, 'unexpected endpoint ' + req.getEndpoint());
        }
    }
}
[/code]